Minor changes, bugfixes
This commit is contained in:
parent
243b0e5c50
commit
8fc94a8c64
21 changed files with 670 additions and 757 deletions
1
INSTALL
1
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:
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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))]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,149 +0,0 @@
|
|||
#!/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/>.
|
||||
|
||||
"""
|
||||
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))
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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)))
|
||||
|
|
|
|||
246
mmgen/util.py
246
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)
|
||||
|
||||
|
|
|
|||
1
setup.py
1
setup.py
|
|
@ -37,7 +37,6 @@ setup(
|
|||
'mmgen.filename',
|
||||
'mmgen.license',
|
||||
'mmgen.mn_electrum',
|
||||
'mmgen.mnemonic',
|
||||
'mmgen.mn_tirosh',
|
||||
'mmgen.obj',
|
||||
'mmgen.opts',
|
||||
|
|
|
|||
663
test/test.py
663
test/test.py
File diff suppressed because it is too large
Load diff
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue