From 4d618ac6f2e5091d3bad02a3dddb74d6a3846af6 Mon Sep 17 00:00:00 2001 From: philemon Date: Wed, 1 Apr 2015 23:24:34 +0300 Subject: [PATCH] Hand merged changes from OO repository new file: obj.py new file: filename.py new file: seed.py --- MANIFEST | 53 ---- mmgen/config.py | 16 +- mmgen/crypto.py | 6 +- mmgen/filename.py | 90 ++++++ mmgen/license.py | 26 +- mmgen/main_addrgen.py | 2 - mmgen/main_addrimport.py | 1 - mmgen/main_passchg.py | 2 - mmgen/main_txcreate.py | 1 - mmgen/main_txsend.py | 3 +- mmgen/main_txsign.py | 2 +- mmgen/main_walletchk.py | 4 +- mmgen/main_walletgen.py | 3 - mmgen/mn_electrum.py | 2 +- mmgen/mn_tirosh.py | 2 +- mmgen/mnemonic.py | 4 +- mmgen/obj.py | 92 ++++++ mmgen/opts.py | 10 + mmgen/seed.py | 630 +++++++++++++++++++++++++++++++++++++++ mmgen/util.py | 154 ++++++---- test/test.py | 8 +- 21 files changed, 938 insertions(+), 173 deletions(-) delete mode 100644 MANIFEST create mode 100755 mmgen/filename.py create mode 100755 mmgen/obj.py create mode 100755 mmgen/seed.py diff --git a/MANIFEST b/MANIFEST deleted file mode 100644 index 2255fca7..00000000 --- a/MANIFEST +++ /dev/null @@ -1,53 +0,0 @@ -# file GENERATED by distutils, do NOT edit -mmgen-addrgen -mmgen-addrimport -mmgen-keygen -mmgen-passchg -mmgen-pywallet -mmgen-tool -mmgen-txcreate -mmgen-txsend -mmgen-txsign -mmgen-walletchk -mmgen-walletgen -setup.py -mmgen/__init__.py -mmgen/addr.py -mmgen/bitcoin.py -mmgen/config.py -mmgen/crypto.py -mmgen/license.py -mmgen/main.py -mmgen/main_addrgen.py -mmgen/main_addrimport.py -mmgen/main_passchg.py -mmgen/main_pywallet.py -mmgen/main_tool.py -mmgen/main_txcreate.py -mmgen/main_txsend.py -mmgen/main_txsign.py -mmgen/main_walletchk.py -mmgen/main_walletgen.py -mmgen/mn_electrum.py -mmgen/mn_tirosh.py -mmgen/mnemonic.py -mmgen/opt.py -mmgen/opts.py -mmgen/term.py -mmgen/test.py -mmgen/tool.py -mmgen/tx.py -mmgen/util.py -mmgen/rpc/__init__.py -mmgen/rpc/config.py -mmgen/rpc/connection.py -mmgen/rpc/data.py -mmgen/rpc/exceptions.py -mmgen/rpc/proxy.py -mmgen/rpc/util.py -mmgen/share/Opts.py -mmgen/share/__init__.py -test/__init__.py -test/gentest.py -test/test.py -test/tooltest.py diff --git a/mmgen/config.py b/mmgen/config.py index 9476f57d..109f9283 100755 --- a/mmgen/config.py +++ b/mmgen/config.py @@ -51,8 +51,9 @@ email = "" Cdates = '2013-2015' version = '0.8.0' -required_opts = [ # list must contain "usr_randchars" - "quiet","verbose","debug","outdir","echo_passphrase","passwd_file","usr_randchars" +required_opts = [ + "quiet","verbose","debug","outdir","echo_passphrase","passwd_file", + "usr_randchars","stdout","show_hash_presets" ] min_screen_width = 80 max_tx_comment_len = 72 @@ -64,7 +65,9 @@ brain_ext = "mmbrain" incog_ext = "mmincog" incog_hex_ext = "mmincox" -seedfile_exts = wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext +seedfile_exts = ( + wallet_ext, seed_ext, mn_ext, brain_ext, incog_ext, incog_hex_ext +) rawtx_ext = "raw" sigtx_ext = "sig" @@ -74,8 +77,8 @@ keyfile_ext = "keys" keyaddrfile_ext = "akeys" mmenc_ext = "mmenc" -default_wl = "electrum" -#default_wl = "tirosh" +default_wordlist = "electrum" +#default_wordlist = "tirosh" # Global value sets user opt dfl_vars = "seed_len","hash_preset","usr_randchars","debug" @@ -93,6 +96,7 @@ max_urandchars,min_urandchars = 80,10 salt_len = 16 aesctr_iv_len = 16 +hincog_chk_len = 8 hash_presets = { # Scrypt params: @@ -117,6 +121,8 @@ max_addr_label_len = 32 wallet_label_symbols = addr_label_symbols max_wallet_label_len = 48 +printable_nospc = [chr(i+33) for i in range(94)] +printable = printable_nospc + [' ','\n','\t'] #addr_label_punc = ".","_",",","-"," ","(",")" #addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc #wallet_label_punc = addr_label_punc diff --git a/mmgen/crypto.py b/mmgen/crypto.py index 6ecd031a..5306454c 100755 --- a/mmgen/crypto.py +++ b/mmgen/crypto.py @@ -66,7 +66,7 @@ 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_checksums(chk1, "of key", key_id, "in header"): + if not compare_chksums(key_id,"key id",chk1,"computed",die=False): msg("Incorrect passphrase") return False @@ -75,12 +75,12 @@ def decrypt_seed(enc_seed, key, seed_id, key_id): chk2 = make_chksum_8(dec_seed) if seed_id: - if compare_checksums(chk2,"of decrypted seed",seed_id,"in header"): + if compare_chksums(seed_id,"seed id",chk2,"decrypted seed",die=False): qmsg("Passphrase is OK") else: if not opt.debug: msg_r("Checking key ID...") - if compare_checksums(chk1, "of key", key_id, "in header"): + if compare_chksums(key_id,"key id",chk1,"computed",die=False): msg("Key ID is correct but decryption of seed failed") else: msg("Incorrect passphrase") diff --git a/mmgen/filename.py b/mmgen/filename.py new file mode 100755 index 00000000..5f77f84a --- /dev/null +++ b/mmgen/filename.py @@ -0,0 +1,90 @@ +#!/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 . + +""" +filename.py: Filename class and methods for the MMGen suite +""" +import sys,os +from mmgen.obj import MMGenObject +import mmgen.config as g +from mmgen.util import msg + +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=""): + import os + self.name = fn + self.dirname = os.path.dirname(fn) + self.basename = os.path.basename(fn) + self.ext = None + + def mf1(k): return k == ftype + def mf2(k): return '.'+k == fn[-len('.'+k):] + # 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.linked_obj = e[k][j] + + if not hasattr(self,attr): + die(2,"Unrecognized %s for file '%s'" % (attr,fn)) + + # TODO: Check for Windows + import stat + if stat.S_ISBLK(os.stat(fn).st_mode): + fd = os.open(fn, os.O_RDONLY) + self.size = os.lseek(fd, 0, os.SEEK_END) + os.close(fd) + else: + self.size = os.stat(fn).st_size diff --git a/mmgen/license.py b/mmgen/license.py index 0748421d..61859b7b 100755 --- a/mmgen/license.py +++ b/mmgen/license.py @@ -17,14 +17,9 @@ # along with this program. If not, see . """ -license.py: Show the license +license.py: Text of GPLv3 """ - -import sys -from mmgen.util import msg, msg_r -from mmgen.term import get_char import mmgen.config as g -import mmgen.opt as opt gpl = { 'warning': """ @@ -587,22 +582,3 @@ Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. """ } - -def do_license_msg(immed=False): - - if opt.quiet or g.no_license: return - - msg(gpl['warning']) - prompt = "%s " % gpl['prompt'].strip() - - while True: - from mmgen.util import my_raw_input - reply = get_char(prompt, immed_chars="wc" if immed else "") - if reply == 'w': - from mmgen.term import do_pager - do_pager(gpl['conditions']) - elif reply == 'c': - msg(""); break - else: - msg_r("\r") - msg("") diff --git a/mmgen/main_addrgen.py b/mmgen/main_addrgen.py index bb537432..6e1725af 100755 --- a/mmgen/main_addrgen.py +++ b/mmgen/main_addrgen.py @@ -25,7 +25,6 @@ import sys import mmgen.config as g import mmgen.opt as opt -from mmgen.license import * from mmgen.util import * from mmgen.crypto import * from mmgen.addr import * @@ -115,7 +114,6 @@ UNENCRYPTED form. Generate only the key(s) you need and guard them carefully. cmd_args = opt.opts.init(opts_data,add_opts=["b16"]) -if opt.show_hash_presets: show_hash_presets() if opt.from_incog_hex or opt.from_incog_hidden: opt.from_incog = True if len(cmd_args) == 1 and any([ diff --git a/mmgen/main_addrimport.py b/mmgen/main_addrimport.py index 8fd3963e..0832fa25 100755 --- a/mmgen/main_addrimport.py +++ b/mmgen/main_addrimport.py @@ -23,7 +23,6 @@ mmgen-addrimport: Import addresses into a MMGen bitcoind tracking wallet import sys, time import mmgen.config as g import mmgen.opt as opt -from mmgen.license import * from mmgen.util import * from mmgen.tx import connect_to_bitcoind from mmgen.addr import AddrInfo,AddrInfoEntry diff --git a/mmgen/main_passchg.py b/mmgen/main_passchg.py index f3a1eb51..f40479a1 100755 --- a/mmgen/main_passchg.py +++ b/mmgen/main_passchg.py @@ -56,8 +56,6 @@ NOTE: The key ID will change if either the passphrase or hash preset are cmd_args = opt.opts.init(opts_data) -if opt.show_hash_presets: show_hash_presets() - if len(cmd_args) != 1: msg("One input file must be specified") sys.exit(2) diff --git a/mmgen/main_txcreate.py b/mmgen/main_txcreate.py index a0d4d27e..f2573193 100755 --- a/mmgen/main_txcreate.py +++ b/mmgen/main_txcreate.py @@ -26,7 +26,6 @@ from decimal import Decimal import mmgen.config as g import mmgen.opt as opt -from mmgen.license import * from mmgen.tx import * opts_data = { diff --git a/mmgen/main_txsend.py b/mmgen/main_txsend.py index b7e807d0..8cc3177a 100755 --- a/mmgen/main_txsend.py +++ b/mmgen/main_txsend.py @@ -24,9 +24,8 @@ import sys import mmgen.config as g import mmgen.opt as opt -from mmgen.license import * from mmgen.tx import * -from mmgen.util import msg,check_infile,get_lines_from_file,confirm_or_exit +from mmgen.util import * opts_data = { 'desc': "Send a Bitcoin transaction signed by {}-txsign".format(g.proj_name.lower()), diff --git a/mmgen/main_txsign.py b/mmgen/main_txsign.py index 3cb8272a..99cd995b 100755 --- a/mmgen/main_txsign.py +++ b/mmgen/main_txsign.py @@ -24,8 +24,8 @@ import sys import mmgen.config as g import mmgen.opt as opt -from mmgen.license import * from mmgen.tx import * +from mmgen.util import do_license_msg opts_data = { 'desc': "Sign Bitcoin transactions generated by {}-txcreate".format(g.proj_name.lower()), diff --git a/mmgen/main_walletchk.py b/mmgen/main_walletchk.py index 4683f619..f5490bc2 100755 --- a/mmgen/main_walletchk.py +++ b/mmgen/main_walletchk.py @@ -156,13 +156,13 @@ else: if opt.export_mnemonic: wl = get_default_wordlist() from mmgen.mnemonic import get_mnemonic_from_seed - mn = get_mnemonic_from_seed(seed, wl, g.default_wl, opt.debug) + mn = get_mnemonic_from_seed(seed, wl, g.default_wordlist, opt.debug) fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext) write_to_file_or_stdout(fn, " ".join(mn)+"\n", "mnemonic data") elif opt.export_seed: from mmgen.bitcoin import b58encode_pad - data = col4(b58encode_pad(seed)) + data = split_into_columns(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_walletgen.py b/mmgen/main_walletgen.py index 81b8a14c..cdb9dd7d 100755 --- a/mmgen/main_walletgen.py +++ b/mmgen/main_walletgen.py @@ -25,7 +25,6 @@ from hashlib import sha256 import mmgen.config as g import mmgen.opt as opt -from mmgen.license import * from mmgen.util import * from mmgen.crypto import * @@ -123,8 +122,6 @@ future, you must continue using these same parameters import mmgen.opt as opt cmd_args = opt.opts.init(opts_data) -if opt.show_hash_presets: show_hash_presets() - if len(cmd_args) == 1: infile = cmd_args[0] check_infile(infile) diff --git a/mmgen/mn_electrum.py b/mmgen/mn_electrum.py index 37dfabff..8c17c671 100755 --- a/mmgen/mn_electrum.py +++ b/mmgen/mn_electrum.py @@ -26,7 +26,7 @@ # list of words from: # http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry -electrum_words = """ +words = """ able about above diff --git a/mmgen/mn_tirosh.py b/mmgen/mn_tirosh.py index b8b82f28..48179e96 100755 --- a/mmgen/mn_tirosh.py +++ b/mmgen/mn_tirosh.py @@ -48,7 +48,7 @@ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN # THE SOFTWARE. -tirosh_words = """ +words = """ abraham absent absorb diff --git a/mmgen/mnemonic.py b/mmgen/mnemonic.py index 2aabe524..679fe7f4 100755 --- a/mmgen/mnemonic.py +++ b/mmgen/mnemonic.py @@ -127,8 +127,8 @@ def check_wordlist(wl,label): Msg("ERROR: List is not sorted!") sys.exit(3) -from mmgen.mn_electrum import electrum_words as el -from mmgen.mn_tirosh import tirosh_words as tl +from mmgen.mn_electrum import words as el +from mmgen.mn_tirosh import words as tl wordlists = sorted(wl_checksums) def get_wordlist(wordlist): diff --git a/mmgen/obj.py b/mmgen/obj.py new file mode 100755 index 00000000..34668edd --- /dev/null +++ b/mmgen/obj.py @@ -0,0 +1,92 @@ +#!/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 . + +""" +obj.py: The MMGenObject class and methods +""" +import mmgen.config as g +from mmgen.util import msgrepr_exit,msgrepr + +lvl = 0 + +class MMGenObject(object): + + # Pretty-print any object of type MMGenObject, recursing into sub-objects + def __str__(self): + global lvl + indent = lvl * " " + + def fix_linebreaks(v,fixed_indent=None): + if "\n" in v: + i = indent+" " if fixed_indent == None else fixed_indent*" " + return "\n"+i + v.replace("\n","\n"+i) + else: return repr(v) + + def conv(v,col_w): + vret = "" + if type(v) == str: + if not (set(list(v)) <= set(list(g.printable))): + vret = repr(v) + else: + vret = fix_linebreaks(v,fixed_indent=0) + elif type(v) == int or type(v) == long: + vret = str(v) + elif type(v) == dict: + sep = "\n{}{}".format(indent," "*4) + cw = max(len(k) for k in v) + 2 + t = sep.join(["{:<{w}}: {}".format( + repr(k), + (fix_linebreaks(v[k],fixed_indent=0) if type(v[k]) == str else v[k]), + w=cw) + for k in sorted(v)]) + vret = "{" + sep + t + "\n" + indent + "}" + elif type(v) in (list,tuple): + sep = "\n{}{}".format(indent," "*4) + t = " ".join([repr(e) for e in sorted(v)]) + o,c = ("[","]") if type(v) == list else ("(",")") + vret = o + sep + t + "\n" + indent + c + elif repr(v)[:14] == '" +# vret = repr(v) + + return vret or type(v) + + out = [] + def f(k): return k[:2] != "__" + keys = filter(f, dir(self)) + 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] == ' +# +# 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 . + +""" +seed.py: Seed-related classes and methods for the MMGen suite +""" +import sys +from binascii import hexlify,unhexlify + +import mmgen.config as g +from mmgen.obj import * +from mmgen.filename import * +from mmgen.util import * +from mmgen.bitcoin import b58encode_pad,b58decode_pad +from mmgen.crypto import * + +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: + die(3,"%s: invalid seed length" % len(seed_bin)) + + 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 + +class SeedSource(MMGenObject): + + class SeedSourceData(MMGenObject): pass + + desc = "seed source" + seed_opts = { + "mnemonic": "Mnemonic", + "brain": "Brainwallet", + "seed": "SeedFile", + "incog": "IncogWallet", + "incog_hex": "IncogWalletHex", + "incog_hidden": "IncogWalletHidden", + } + + def __init__(self,fn=None,seed=None,passwd=None): + + self.ssdata = self.SeedSourceData() + + if seed: + self.desc = "new " + self.desc + self.seed = seed + self.ssdata.passwd = passwd + self._pre_encode() + self._encode() + else: + self._get_formatted_data(fn) + self._deformat() + self._decode() + + def _get_formatted_data(self,fn): + if fn: + self.infile = fn + self.fmt_data = get_data_from_file(fn.name,self.desc) + else: + self.infile = None + self.fmt_data = get_data_from_user(self.desc) + + def _pre_encode(self): pass + + def init(cls,fn=None,seed=None,passwd=None): + + sstype = None + sopts=["%s_%s" % (l,k) for k in cls.seed_opts for l in "from","export"] + for o in sopts: + if o in opt.__dict__ and opt.__dict__[o]: + sstype = cls.seed_opts[o.split("_",1)[1]] + break + + if seed: + return globals()[sstype or "Wallet"](seed=seed) + else: + if fn: + if opt.from_incog_hidden: + fn = Filename(fn,ftype="hincog") + else: + fn = Filename(fn) + sstype = fn.linked_obj + return globals()[sstype](fn=fn) + else: + return globals()[sstype or "Wallet"]() + + init = classmethod(init) + + def write_to_file(self): + self._format() + write_to_file_or_stdout(self._filename(),self.fmt_data, self.desc) + +class SeedSourceUnenc(SeedSource): pass + +class SeedSourceEnc(SeedSource): + + _ss_enc_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. + """.strip() + } + + def _pre_encode(self): + if not self.ssdata.passwd: + self._get_hash_preset() + self._get_first_passwd() + self._encrypt_seed() + + 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): + self.ssdata.hash_preset = \ + opt.hash_preset or get_hash_preset_from_user(what=self.desc) + + def _encrypt_seed(self): + d = self.ssdata + 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): + + desc = "mnemonic data" + wl_checksums = { + "electrum": '5ca31424', + "tirosh": '1a5faeff' + } + 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 + + def _baseNtohex(self,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 "%s%s" % (('0' if len(ret) % 2 else ''), ret) + + def _hextobaseN(self,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: + die(1,'"%s": invalid wordlist. Valid choices: %s' % + (wordlist,'"'+'" "'.join(self.wordlists)+'"')) + + if wordlist == "electrum": + from mmgen.mn_electrum import words + elif wordlist == "tirosh": + from mmgen.mn_tirosh import words + else: + die(3,"Internal error: unknown wordlist") + + return words.strip().split("\n") + + def _encode(self): + + wl = self._get_wordlist() + seed_hex = hexlify(self.seed.data) + 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) + + self.ssdata.mnemonic = mn + + def _format(self): + self.fmt_data = " ".join(self.ssdata.mnemonic) + "\n" + + def _deformat(self): + + mn = self.fmt_data.split() + wl = self._get_wordlist() + + if len(mn) not in g.mn_lens: + die(3,"Invalid mnemonic (%i words). Allowed numbers of words: %s" % + (len(mn),", ".join([str(i) for i in g.mn_lens]))) + + 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() + + 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)) + + qmsg("Valid mnemonic for seed ID %s" % make_chksum_8(unhexlify(seed_hex))) + + self.seed = Seed(unhexlify(seed_hex)) + + def _filename(self): + return "%s.%s" % (self.seed.sid, g.mn_ext) + +class SeedFile (SeedSourceUnenc): + + desc = "seed data" + + def _encode(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) + ) + + def _deformat(self): + what = 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)) + return False + + a,b = ld[0],"".join(ld[1:]) + + if not is_chksum_6(a): + msg("'%s': invalid checksum format, in %s" % (a, what)) + return False + + if not is_b58string(b): + msg("'%s': not a base 58 string, in %s" % (b, what)) + return False + + vmsg_r("Validating %s checksum..." % what) + + compare_chksums(a,"checksum",make_chksum_6(b),"base 58 data") + + self.ssdata.chksum = a + self.ssdata.b58seed = b + + def _filename(self): + return "%s.%s" % (self.seed.sid, g.seed_ext) + +class Wallet (SeedSourceEnc): + + desc = "%s wallet" % g.proj_name + + def _encode(self): + d = self.ssdata + d.label = opt.label or "No Label" + d.pw_status = "NE" if len(d.passwd) else "E" + d.timestamp = make_timestamp() + + def _format(self): + d = self.ssdata + s = self.seed + s_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), + "{}: {} {} {}".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)) + ) + 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)) + + 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 {} wallet data from file '{}'".format( + g.proj_name,self.infile.name)) + + lines = self.fmt_data.rstrip().split("\n") + + self._check_master_chksum(lines) + + d = self.ssdata + d.label = lines[1] + + d1,d2,d3,d4,d5 = lines[2].split() + d.seed_id = d1.upper() + d.key_id = d2.upper() + d.seed_len = int(d3) + d.pw_status,d.timestamp = d4,d5 + + hpdata = lines[3].split() + d.hash_preset = hpdata[0][:-1] # a string! + 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) + + 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) + + def _filename(self): + return "{}-{}[{},{}].{}".format( + self.seed.sid, + self.ssdata.key_id, + self.seed.len_bits, + self.ssdata.hash_preset, + g.wallet_ext + ) + +# def __str__(self): +## label,metadata,hash_preset,salt,enc_seed): +# d = self.ssdata +# s = self.seed +# out = ["WALLET DATA"] +# fs = " {:18} {}" +# pw_empty = "Yes" if d.metadata[3] == "E" else "No" +# for i in ( +# ("Label:", d.label), +# ("Seed ID:", s.sid), +# ("Key ID:", d.key_id), +# ("Seed length:", "%s bits (%s bytes)" % (s.len_bits,s.len_bytes)), +# ("Scrypt params:", "Preset '%s' (%s)" % (opt.hash_preset, +# " ".join([str(i) for i in get_hash_params(opt.hash_preset)]) +# ) +# ), +# ("Passphrase empty?", pw_empty), +# ("Timestamp:", "%s UTC" % d.metadata[4]), +# ): out.append(fs.format(*i)) +# +# fs = " {:6} {}" +# for i in ( +# ("Salt:", ""), +# (" b58:", b58encode_pad(d.salt)), +# (" hex:", hexlify(d.salt)), +# ("Encrypted seed:", ""), +# (" b58:", b58encode_pad(d.enc_seed)), +# (" hex:", hexlify(d.enc_seed)) +# ): out.append(fs.format(*i)) +# +# return "\n".join(out) + +class Brainwallet (SeedSourceEnc): + + desc = "brainwallet" + + def _deformat(self): + self.brainpasswd = " ".join(self.fmt_data.split()) + + def _decode(self): + self._get_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(self.brainpasswd, "", + self.ssdata.hash_preset, buflen=opt.seed_len/8) + vmsg("Done") + self.seed = Seed(seed) + + +class IncogWallet (SeedSourceEnc): + + desc = "incognito wallet" + + _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': """ +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 +to exit and re-run the program with the '--old-incog-fmt' option. +""".strip(), + } + + 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 + + def _encode (self): + 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) + + d.salt = get_random(g.salt_len) + key = make_key(d.passwd, d.salt, d.hash_preset, "incog wallet key") + chk = sha256(self.seed.data).digest()[:8] + d.enc_seed = encrypt_data(chk + self.seed.data, key, 1, "seed") + + 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.data_len = self._get_incog_data_len(opt.seed_len) + + def _format(self): + d = self.ssdata + self.fmt_data = d.iv + encrypt_data( + d.salt + d.enc_seed, + d.wrapper_key, + int(hexlify(d.iv),16), + "incog data" + ) + + def _filename(self): + return "{}-{}-{}[{},{}].{}".format( + self.seed.sid, + self.ssdata.key_id, + self.ssdata.iv_id, + self.seed.len_bits, + self.ssdata.hash_preset, + g.incog_ext + ) + + def _deformat(self): + + # Data could be of invalid length, so check: + valid_dlens = map(self._get_incog_data_len, g.seed_lens) + # => [56, 64, 72] + raw_d = self.fmt_data + if len(raw_d) not in valid_dlens: + die(1, + "Invalid incognito file size: %s. Valid sizes (in bytes): %s" % + (len(raw_d), " ".join(map(str, valid_dlens)))) + + d = self.ssdata + d.iv = raw_d[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.") + k = 'incog_iv_id_hidden' if opt.from_incog_hidden else 'incog_iv_id' + vmsg("\n%s\n" % self._icg_msg[k]) + + def _decode(self): + d = self.ssdata + prompt_info="{} incognito wallet".format(g.proj_name) + + while True: + passwd = get_mmgen_passphrase(prompt_info+" "+d.incog_id) + + qmsg("Configured hash presets: %s" % + " ".join(sorted(g.hash_presets))) + d.hash_preset = get_hash_preset_from_user(what="incog wallet") + + # 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") + + d.salt = dd[0:g.salt_len] + d.enc_seed = dd[g.salt_len:] + + key = make_key(passwd, d.salt, d.hash_preset, "main key") + vmsg("Key ID: %s" % make_chksum_8(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) + + +class IncogWalletHex (IncogWallet): + + def _deformat(self): + self.fmt_data = decode_pretty_hexdump(self.fmt_data) + IncogWallet._deformat(self) + + +class IncogWalletHidden (IncogWallet): + + def _parse_hincog_opt(self): + class HincogParams(MMGenObject): pass + o = opt.from_incog_hidden or opt.export_incog_hidden + p = HincogParams() + a,b = o.split(",") + p.filename = a + p.offset = int(b) + return p + + def _check_valid_offset(self,fn,action): + d = self.ssdata + if fn.size < d.hincog_offset + d.data_len: + die(1, +"Destination file has length %s, too short to %s %s bytes of data at offset %s" + % (f.size,action,d.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") + d = self.ssdata + p = self._parse_hincog_opt() + d.hincog_offset = p.offset + self.infile = Filename(p.filename,ftype="hincog") + + qmsg("Getting hidden incog data from file '%s'" % self.infile.name) + + # Already sanity-checked: + d.data_len = self._get_incog_data_len(opt.seed_len) + self._check_valid_offset(self.infile,"read") + + import os + fh = os.open(self.infile.name,os.O_RDONLY) + os.lseek(fh,int(p.offset),os.SEEK_SET) + self.fmt_data = os.read(fh,d.data_len) + os.close(fh) + qmsg("Data read from file '%s' at offset %s" % + (self.infile.name,p.offset), "Data read from file") + + + # overrides method in SeedSource + def write_to_file(self): + d = self.ssdata + self._format() + compare_or_die(d.data_len, "target data length", + len(self.fmt_data),"length of formatted " + self.desc) + p = self._parse_hincog_opt() + d.hincog_offset = p.offset + self.outfile = f = Filename(p.filename,ftype="hincog") + + if opt.debug: + Msg("Incog data len %s, offset %s" % (d.data_len,p.offset)) + self._check_valid_offset(f,"write") + + if not opt.quiet: confirm_or_exit("","alter file '%s'" % f.name) + import os + fh = os.open(f.name,os.O_RDWR) + os.lseek(fh, int(p.offset), os.SEEK_SET) + os.write(fh, self.fmt_data) + os.close(fh) + msg("Data written to file '%s' at offset %s" % (f.name,p.offset)) diff --git a/mmgen/util.py b/mmgen/util.py index 8493d9a8..0081e39b 100755 --- a/mmgen/util.py +++ b/mmgen/util.py @@ -20,9 +20,10 @@ util.py: Low-level routines imported by other modules for the MMGen suite """ -import sys +import sys,os,time,stat,re from hashlib import sha256 from binascii import hexlify,unhexlify +from string import hexdigits import mmgen.config as g @@ -34,10 +35,25 @@ def green(s): return _grn+s+_reset def yellow(s): return _yel+s+_reset def cyan(s): return _cya+s+_reset -def msgred(s): sys.stderr.write(red(s+"\n")) - def msg(s): sys.stderr.write(s+"\n") 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): + for d in args: + sys.stdout.write(repr(d)+"\n") +def msgrepr_exit(*args): + for d in args: + sys.stdout.write(repr(d)+"\n") + sys.exit() + +def die(ev,s): + sys.stderr.write(s+"\n"); sys.exit(ev) +def Die(ev,s): + sys.stdout.write(s+"\n"); sys.exit(ev) + +import opt def qmsg(s,alt=False): if opt.quiet: if alt != False: sys.stderr.write(alt + "\n") @@ -51,26 +67,11 @@ def vmsg(s): def vmsg_r(s): if opt.verbose: sys.stderr.write(s) -def Msg(s): sys.stdout.write(s + "\n") -def Msg_r(s): sys.stdout.write(s) def Vmsg(s): if opt.verbose: sys.stdout.write(s + "\n") def Vmsg_r(s): if opt.verbose: sys.stdout.write(s) -def die(ev,s): - sys.stderr.write(s+"\n"); sys.exit(ev) -def Die(ev,s): - sys.stdout.write(s+"\n"); sys.exit(ev) - -def msgrepr(*args): - for d in args: - sys.stdout.write(repr(d)+"\n") -def msgrepr_exit(*args): - for d in args: - sys.stdout.write(repr(d)+"\n") - sys.exit() - def suf(arg,what): t = type(arg) if t == int: @@ -87,7 +88,6 @@ def suf(arg,what): return "" if n == 1 else "s" def get_extension(f): - import os return os.path.splitext(f)[1][1:] def make_chksum_N(s,n,sep=False): @@ -100,6 +100,8 @@ def make_chksum_8(s,sep=False): s = sha256(sha256(s).digest()).hexdigest()[:8].upper() return "{} {}".format(s[:4],s[4:]) if sep else s def make_chksum_6(s): return sha256(s).hexdigest()[:6] +def is_chksum_6(s): return len(s) == 6 and is_hexstring_lc(s) + def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper() def splitN(s,n,sep=None): # always return an n-element list @@ -108,25 +110,31 @@ 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 col4(s): - nondiv = 1 if len(s) % 4 else 0 - return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)]) +def split_into_columns(col_wid,s): + return " ".join([s[col_wid*i:col_wid*(i+1)] + for i in range(len(s)/col_wid+1)]).rstrip() def make_timestamp(): - import time tv = time.gmtime(time.time())[:6] return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv) def make_timestr(): - import time tv = time.gmtime(time.time())[:6] return "{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}".format(*tv) def secs_to_hms(secs): return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60) -def _is_hex(s): - try: int(s,16) - except: return False - else: return True +def _is_whatstring(s,chars): + return set(list(s)) <= set(chars) + +def is_hexstring(s): + return _is_whatstring(s.lower(),hexdigits.lower()) +def is_hexstring_lc(s): + return _is_whatstring(s,hexdigits.lower()) +def is_hexstring_uc(s): + return _is_whatstring(s,hexdigits.upper()) +def is_b58string(s): + from mmgen.bitcoin import b58a + return _is_whatstring(s,b58a) def is_utf8(s): try: s.decode("utf8") @@ -154,7 +162,6 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False): ).rstrip() def decode_pretty_hexdump(data): - import re from string import hexdigits lines = [re.sub('^['+hexdigits+']+:\s+','',l) for l in data.split("\n")] return unhexlify("".join(("".join(lines).split()))) @@ -166,32 +173,29 @@ def get_hash_params(hash_preset): msg("%s: invalid 'hash_preset' value" % hash_preset) sys.exit(3) -def show_hash_presets(): - fs = " {:<7} {:<6} {:<3} {}" - msg("Available parameters for scrypt.hash():") - msg(fs.format("Preset","N","r","p")) - for i in sorted(g.hash_presets.keys()): - msg(fs.format("'%s'" % i, *g.hash_presets[i])) - msg("N = memory usage (power of two), p = iterations (rounds)") - sys.exit(0) +def compare_chksums(chk1, desc1, chk2, desc2, die=True): -def compare_checksums(chksum1, desc1, chksum2, desc2): + 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 - if chksum1.lower() == chksum2.lower(): - vmsg("OK (%s)" % chksum1.upper()) - return True - else: - if opt.debug: - Msg( - "ERROR: Computed checksum %s (%s) doesn't match checksum %s (%s)" - % (desc1,chksum1,desc2,chksum2)) - return False + vmsg("%s checksum OK (%s)" % (desc1.capitalize(),chk1)) + return True + +def compare_or_die(val1, desc1, val2, desc2): + 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)) + return True def get_default_wordlist(): - wl_id = g.default_wl - if wl_id == "electrum": from mmgen.mn_electrum import electrum_words as wl - elif wl_id == "tirosh": from mmgen.mn_tirosh import tirosh_words as wl + 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): @@ -250,7 +254,6 @@ def _validate_addr_num(n): def make_full_path(outdir,outfile): - import os return os.path.normpath(os.path.join(outdir, os.path.basename(outfile))) @@ -282,7 +285,7 @@ def parse_addr_idxs(arg,sep=","): return sorted(set(ret)) -def get_new_passphrase(what, passchg=False): +def get_new_passphrase(what,passchg=False): w = "{}passphrase for {}".format("new " if passchg else "", what) if opt.passwd_file: @@ -330,7 +333,6 @@ def write_to_stdout(data, what, confirm=True): confirm_or_exit("",'output {} to screen'.format(what)) elif not sys.stdout.isatty(): try: - import os 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 @@ -344,8 +346,7 @@ def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False if opt.outdir: outfile = make_full_path(opt.outdir,outfile) - from os import stat - try: stat(outfile) + try: os.stat(outfile) except: pass else: if confirm_overwrite: @@ -424,8 +425,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), col4(sf)), - "{} {}".format(make_chksum_6(esf), col4(esf)) + "{} {}".format(make_chksum_6(sf), split_into_columns(4,sf)), + "{} {}".format(make_chksum_6(esf), split_into_columns(4,esf)) ) chk = make_chksum_6(" ".join(lines)) @@ -450,7 +451,7 @@ def _check_mmseed_format(words): if len(words) < 3 or len(words) > 12: msg("Invalid data length (%s) in %s" % (len(words),what)) - elif not _is_hex(words[0]): + elif not is_hexstring(words[0]): msg("Invalid format of checksum '%s' in %s"%(words[0], what)) elif chklen != 6: msg("Incorrect length of checksum (%s) in %s" % (chklen,what)) @@ -468,7 +469,7 @@ def _check_wallet_format(infile, lines): vmsg("Invalid number of lines (%s) in %s" % (len(lines),what)) elif chklen != 6: vmsg("Incorrect length of Master checksum (%s) in %s" % (chklen,what)) - elif not _is_hex(lines[0]): + elif not is_hexstring(lines[0]): vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], what)) else: valid = True @@ -555,7 +556,6 @@ def get_words(infile,what,prompt): return _get_words_from_user(prompt) def remove_comments(lines): - import re # re.sub(pattern, repl, string, count=0, flags=0) ret = [] for i in lines: @@ -573,6 +573,11 @@ def get_lines_from_file(infile,what="",trim_comments=False): 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) + return data + def get_data_from_file(infile,what="data",dash=False,silent=False): if dash and infile == "-": return sys.stdin.read() if not silent: @@ -595,7 +600,7 @@ def get_seed_from_seed_data(words): chk = make_chksum_6(seed_b58) vmsg_r("Validating %s checksum..." % g.seed_ext) - if compare_checksums(chk, "from seed", stored_chk, "from input"): + if compare_chksums(chk, "seed", stored_chk, "input",die=False): seed = b58decode_pad(seed_b58) if seed == False: msg("Invalid b58 number: %s" % val) @@ -639,7 +644,6 @@ def get_bitcoind_passphrase(prompt): def check_data_fits_file_at_offset(fname,offset,dlen,action): # TODO: Check for Windows - import os, stat if stat.S_ISBLK(os.stat(fname).st_mode): fd = os.open(fname, os.O_RDONLY) fsize = os.lseek(fd, 0, os.SEEK_END) @@ -656,9 +660,9 @@ def check_data_fits_file_at_offset(fname,offset,dlen,action): from mmgen.term import kb_hold_protect,get_char -def get_hash_preset_from_user(hp='3',what="data"): - p = "Enter hash preset for %s, or ENTER to accept the default ('%s'): " \ - % (what,hp) +def get_hash_preset_from_user(hp=g.hash_preset,what="data"): + p = """Enter hash preset for %s, +or hit ENTER to accept the default value ('%s'): """ % (what,hp) while True: ret = my_raw_input(p) if ret: @@ -721,3 +725,23 @@ def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False): if verbose: msg("\nInvalid reply") else: msg_r("\r") + + +def do_license_msg(immed=False): + + from mmgen.license import gpl + if opt.quiet or g.no_license: return + + msg(gpl['warning']) + prompt = "%s " % gpl['prompt'].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']) + elif reply == 'c': + msg(""); break + else: + msg_r("\r") + msg("") diff --git a/test/test.py b/test/test.py index 6dd9f2d0..cdaf43f2 100755 --- a/test/test.py +++ b/test/test.py @@ -407,8 +407,8 @@ class MMGenExpect(object): passphrase+"\n",regex=True) def hash_preset(self,what,preset=''): - my_expect(self.p,("Enter hash preset for %s, or ENTER .*?:" % what), - str(preset)+"\n",regex=True) + my_expect(self.p,("Enter hash preset for %s," % what)) + 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 @@ -505,8 +505,8 @@ def add_comments_to_addr_file(addrfile,tfile): def make_brainwallet_file(fn): # Print random words with random whitespace in between - from mmgen.mn_tirosh import tirosh_words - wl = tirosh_words.split("\n") + from mmgen.mn_tirosh import words + wl = words.split("\n") nwords,ws_list,max_spaces = 10," \n",5 def rand_ws_seq(): nchars = getrandnum(1) % max_spaces + 1