Hand merged changes from OO repository

new file:   obj.py
	new file:   filename.py
	new file:   seed.py
This commit is contained in:
The MMGen Project 2015-04-01 23:24:34 +03:00
commit 4d618ac6f2
21 changed files with 938 additions and 173 deletions

View file

@ -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

View file

@ -51,8 +51,9 @@ email = "<mmgen-py@yandex.com>"
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

View file

@ -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")

90
mmgen/filename.py Executable file
View file

@ -0,0 +1,90 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
#
# 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 <http://www.gnu.org/licenses/>.
"""
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

View file

@ -17,14 +17,9 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
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("")

View file

@ -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([

View file

@ -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

View file

@ -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)

View file

@ -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 = {

View file

@ -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()),

View file

@ -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()),

View file

@ -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")

View file

@ -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)

View file

@ -26,7 +26,7 @@
# list of words from:
# http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry
electrum_words = """
words = """
able
about
above

View file

@ -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

View file

@ -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):

92
mmgen/obj.py Executable file
View file

@ -0,0 +1,92 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
#
# 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 <http://www.gnu.org/licenses/>.
"""
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] == '<bound method ':
vret = " ".join(repr(v).split()[0:3]) + ">"
# 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] == '<bound method ']
def f(k): return repr(getattr(self,k))[:14] == '<bound method '
methods = filter(f,keys)
def f(k): return repr(getattr(self,k))[:7] == '<mmgen.'
objects = filter(f,keys)
other = list(set(keys) - set(methods) - set(objects))
for k in sorted(methods) + sorted(other) + sorted(objects):
val = getattr(self,k)
if str(type(val))[:13] == "<class 'mmgen": # recurse into sub-objects
out.append("\n%s%s (%s):" % (indent,k,repr(type(val))))
lvl += 1
out.append(str(getattr(self,k))+"\n")
lvl -= 1
else:
out.append(fs % (k, conv(val,col_w)))
return "\n".join(out)

View file

@ -66,6 +66,15 @@ def typeconvert_from_dfl(key):
msg(fs % (opt.__dict__[key],opt.replace("_","-"),m))
sys.exit(1)
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 init(opts_data,add_opts=[]):
if len(sys.argv) == 2 and sys.argv[1] == '--version':
@ -113,6 +122,7 @@ def init(opts_data,add_opts=[]):
typeconvert_from_dfl(k)
else: opt.__dict__[k] = g.__dict__[k]
if opt.show_hash_presets: _show_hash_presets()
if opt.debug: opt.verbose = True
if g.debug:

630
mmgen/seed.py Executable file
View file

@ -0,0 +1,630 @@
#!/usr/bin/env python
#
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
# Copyright (C)2013-2015 Philemon <mmgen-py@yandex.com>
#
# 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 <http://www.gnu.org/licenses/>.
"""
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))

View file

@ -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("")

View file

@ -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