Hand merged changes from OO repository
new file: obj.py new file: filename.py new file: seed.py
This commit is contained in:
parent
cb035263f9
commit
4d618ac6f2
21 changed files with 938 additions and 173 deletions
53
MANIFEST
53
MANIFEST
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
90
mmgen/filename.py
Executable 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
|
||||
|
|
@ -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("")
|
||||
|
|
|
|||
|
|
@ -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([
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
# list of words from:
|
||||
# http://en.wiktionary.org/wiki/Wiktionary:Frequency_lists/Contemporary_poetry
|
||||
|
||||
electrum_words = """
|
||||
words = """
|
||||
able
|
||||
about
|
||||
above
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
92
mmgen/obj.py
Executable 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)
|
||||
|
|
@ -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
630
mmgen/seed.py
Executable 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))
|
||||
154
mmgen/util.py
154
mmgen/util.py
|
|
@ -20,9 +20,10 @@
|
|||
util.py: Low-level routines imported by other modules for the MMGen suite
|
||||
"""
|
||||
|
||||
import sys
|
||||
import sys,os,time,stat,re
|
||||
from hashlib import sha256
|
||||
from binascii import hexlify,unhexlify
|
||||
from string import hexdigits
|
||||
|
||||
import mmgen.config as g
|
||||
|
||||
|
|
@ -34,10 +35,25 @@ def green(s): return _grn+s+_reset
|
|||
def yellow(s): return _yel+s+_reset
|
||||
def cyan(s): return _cya+s+_reset
|
||||
|
||||
def msgred(s): sys.stderr.write(red(s+"\n"))
|
||||
|
||||
def msg(s): sys.stderr.write(s+"\n")
|
||||
def msg_r(s): sys.stderr.write(s)
|
||||
def Msg(s): sys.stdout.write(s + "\n")
|
||||
def Msg_r(s): sys.stdout.write(s)
|
||||
def msgred(s): sys.stderr.write(red(s+"\n"))
|
||||
def msgrepr(*args):
|
||||
for d in args:
|
||||
sys.stdout.write(repr(d)+"\n")
|
||||
def msgrepr_exit(*args):
|
||||
for d in args:
|
||||
sys.stdout.write(repr(d)+"\n")
|
||||
sys.exit()
|
||||
|
||||
def die(ev,s):
|
||||
sys.stderr.write(s+"\n"); sys.exit(ev)
|
||||
def Die(ev,s):
|
||||
sys.stdout.write(s+"\n"); sys.exit(ev)
|
||||
|
||||
import opt
|
||||
def qmsg(s,alt=False):
|
||||
if opt.quiet:
|
||||
if alt != False: sys.stderr.write(alt + "\n")
|
||||
|
|
@ -51,26 +67,11 @@ def vmsg(s):
|
|||
def vmsg_r(s):
|
||||
if opt.verbose: sys.stderr.write(s)
|
||||
|
||||
def Msg(s): sys.stdout.write(s + "\n")
|
||||
def Msg_r(s): sys.stdout.write(s)
|
||||
def Vmsg(s):
|
||||
if opt.verbose: sys.stdout.write(s + "\n")
|
||||
def Vmsg_r(s):
|
||||
if opt.verbose: sys.stdout.write(s)
|
||||
|
||||
def die(ev,s):
|
||||
sys.stderr.write(s+"\n"); sys.exit(ev)
|
||||
def Die(ev,s):
|
||||
sys.stdout.write(s+"\n"); sys.exit(ev)
|
||||
|
||||
def msgrepr(*args):
|
||||
for d in args:
|
||||
sys.stdout.write(repr(d)+"\n")
|
||||
def msgrepr_exit(*args):
|
||||
for d in args:
|
||||
sys.stdout.write(repr(d)+"\n")
|
||||
sys.exit()
|
||||
|
||||
def suf(arg,what):
|
||||
t = type(arg)
|
||||
if t == int:
|
||||
|
|
@ -87,7 +88,6 @@ def suf(arg,what):
|
|||
return "" if n == 1 else "s"
|
||||
|
||||
def get_extension(f):
|
||||
import os
|
||||
return os.path.splitext(f)[1][1:]
|
||||
|
||||
def make_chksum_N(s,n,sep=False):
|
||||
|
|
@ -100,6 +100,8 @@ def make_chksum_8(s,sep=False):
|
|||
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
|
||||
return "{} {}".format(s[:4],s[4:]) if sep else s
|
||||
def make_chksum_6(s): return sha256(s).hexdigest()[:6]
|
||||
def is_chksum_6(s): return len(s) == 6 and is_hexstring_lc(s)
|
||||
|
||||
def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
|
||||
|
||||
def splitN(s,n,sep=None): # always return an n-element list
|
||||
|
|
@ -108,25 +110,31 @@ def splitN(s,n,sep=None): # always return an n-element list
|
|||
def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
|
||||
def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
|
||||
|
||||
def col4(s):
|
||||
nondiv = 1 if len(s) % 4 else 0
|
||||
return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)])
|
||||
def split_into_columns(col_wid,s):
|
||||
return " ".join([s[col_wid*i:col_wid*(i+1)]
|
||||
for i in range(len(s)/col_wid+1)]).rstrip()
|
||||
|
||||
def make_timestamp():
|
||||
import time
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
|
||||
def make_timestr():
|
||||
import time
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
return "{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}".format(*tv)
|
||||
def secs_to_hms(secs):
|
||||
return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
|
||||
|
||||
def _is_hex(s):
|
||||
try: int(s,16)
|
||||
except: return False
|
||||
else: return True
|
||||
def _is_whatstring(s,chars):
|
||||
return set(list(s)) <= set(chars)
|
||||
|
||||
def is_hexstring(s):
|
||||
return _is_whatstring(s.lower(),hexdigits.lower())
|
||||
def is_hexstring_lc(s):
|
||||
return _is_whatstring(s,hexdigits.lower())
|
||||
def is_hexstring_uc(s):
|
||||
return _is_whatstring(s,hexdigits.upper())
|
||||
def is_b58string(s):
|
||||
from mmgen.bitcoin import b58a
|
||||
return _is_whatstring(s,b58a)
|
||||
|
||||
def is_utf8(s):
|
||||
try: s.decode("utf8")
|
||||
|
|
@ -154,7 +162,6 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
|
|||
).rstrip()
|
||||
|
||||
def decode_pretty_hexdump(data):
|
||||
import re
|
||||
from string import hexdigits
|
||||
lines = [re.sub('^['+hexdigits+']+:\s+','',l) for l in data.split("\n")]
|
||||
return unhexlify("".join(("".join(lines).split())))
|
||||
|
|
@ -166,32 +173,29 @@ def get_hash_params(hash_preset):
|
|||
msg("%s: invalid 'hash_preset' value" % hash_preset)
|
||||
sys.exit(3)
|
||||
|
||||
def show_hash_presets():
|
||||
fs = " {:<7} {:<6} {:<3} {}"
|
||||
msg("Available parameters for scrypt.hash():")
|
||||
msg(fs.format("Preset","N","r","p"))
|
||||
for i in sorted(g.hash_presets.keys()):
|
||||
msg(fs.format("'%s'" % i, *g.hash_presets[i]))
|
||||
msg("N = memory usage (power of two), p = iterations (rounds)")
|
||||
sys.exit(0)
|
||||
def compare_chksums(chk1, desc1, chk2, desc2, die=True):
|
||||
|
||||
def compare_checksums(chksum1, desc1, chksum2, desc2):
|
||||
if not chk1 == chk2:
|
||||
if die:
|
||||
die(3,"Checksum error: %s checksum (%s) doesn't match %s checksum (%s)"
|
||||
% (desc2,chk2,desc1,chk1))
|
||||
else: return False
|
||||
|
||||
if chksum1.lower() == chksum2.lower():
|
||||
vmsg("OK (%s)" % chksum1.upper())
|
||||
return True
|
||||
else:
|
||||
if opt.debug:
|
||||
Msg(
|
||||
"ERROR: Computed checksum %s (%s) doesn't match checksum %s (%s)"
|
||||
% (desc1,chksum1,desc2,chksum2))
|
||||
return False
|
||||
vmsg("%s checksum OK (%s)" % (desc1.capitalize(),chk1))
|
||||
return True
|
||||
|
||||
def compare_or_die(val1, desc1, val2, desc2):
|
||||
if cmp(val1,val2):
|
||||
die(3,"Error: %s (%s) doesn't match %s (%s)"
|
||||
% (desc2,val2,desc1,val1))
|
||||
vmsg("%s OK (%s)" % (desc2.capitalize(),val2))
|
||||
return True
|
||||
|
||||
def get_default_wordlist():
|
||||
|
||||
wl_id = g.default_wl
|
||||
if wl_id == "electrum": from mmgen.mn_electrum import electrum_words as wl
|
||||
elif wl_id == "tirosh": from mmgen.mn_tirosh import tirosh_words as wl
|
||||
wl_id = g.default_wordlist
|
||||
if wl_id == "electrum": from mmgen.mn_electrum import words as wl
|
||||
elif wl_id == "tirosh": from mmgen.mn_tirosh import words as wl
|
||||
return wl.strip().split("\n")
|
||||
|
||||
def open_file_or_exit(filename,mode):
|
||||
|
|
@ -250,7 +254,6 @@ def _validate_addr_num(n):
|
|||
|
||||
|
||||
def make_full_path(outdir,outfile):
|
||||
import os
|
||||
return os.path.normpath(os.path.join(outdir, os.path.basename(outfile)))
|
||||
|
||||
|
||||
|
|
@ -282,7 +285,7 @@ def parse_addr_idxs(arg,sep=","):
|
|||
return sorted(set(ret))
|
||||
|
||||
|
||||
def get_new_passphrase(what, passchg=False):
|
||||
def get_new_passphrase(what,passchg=False):
|
||||
|
||||
w = "{}passphrase for {}".format("new " if passchg else "", what)
|
||||
if opt.passwd_file:
|
||||
|
|
@ -330,7 +333,6 @@ def write_to_stdout(data, what, confirm=True):
|
|||
confirm_or_exit("",'output {} to screen'.format(what))
|
||||
elif not sys.stdout.isatty():
|
||||
try:
|
||||
import os
|
||||
of = os.readlink("/proc/%d/fd/1" % os.getpid())
|
||||
of_maybe = os.path.relpath(of)
|
||||
of = of if of_maybe.find(os.path.pardir) == 0 else of_maybe
|
||||
|
|
@ -344,8 +346,7 @@ def write_to_file(outfile,data,what="data",confirm_overwrite=False,verbose=False
|
|||
|
||||
if opt.outdir: outfile = make_full_path(opt.outdir,outfile)
|
||||
|
||||
from os import stat
|
||||
try: stat(outfile)
|
||||
try: os.stat(outfile)
|
||||
except: pass
|
||||
else:
|
||||
if confirm_overwrite:
|
||||
|
|
@ -424,8 +425,8 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed):
|
|||
label,
|
||||
"{} {} {} {} {}".format(*metadata),
|
||||
"{}: {} {} {}".format(hash_preset,*get_hash_params(hash_preset)),
|
||||
"{} {}".format(make_chksum_6(sf), col4(sf)),
|
||||
"{} {}".format(make_chksum_6(esf), col4(esf))
|
||||
"{} {}".format(make_chksum_6(sf), split_into_columns(4,sf)),
|
||||
"{} {}".format(make_chksum_6(esf), split_into_columns(4,esf))
|
||||
)
|
||||
|
||||
chk = make_chksum_6(" ".join(lines))
|
||||
|
|
@ -450,7 +451,7 @@ def _check_mmseed_format(words):
|
|||
|
||||
if len(words) < 3 or len(words) > 12:
|
||||
msg("Invalid data length (%s) in %s" % (len(words),what))
|
||||
elif not _is_hex(words[0]):
|
||||
elif not is_hexstring(words[0]):
|
||||
msg("Invalid format of checksum '%s' in %s"%(words[0], what))
|
||||
elif chklen != 6:
|
||||
msg("Incorrect length of checksum (%s) in %s" % (chklen,what))
|
||||
|
|
@ -468,7 +469,7 @@ def _check_wallet_format(infile, lines):
|
|||
vmsg("Invalid number of lines (%s) in %s" % (len(lines),what))
|
||||
elif chklen != 6:
|
||||
vmsg("Incorrect length of Master checksum (%s) in %s" % (chklen,what))
|
||||
elif not _is_hex(lines[0]):
|
||||
elif not is_hexstring(lines[0]):
|
||||
vmsg("Invalid format of Master checksum '%s' in %s"%(lines[0], what))
|
||||
else: valid = True
|
||||
|
||||
|
|
@ -555,7 +556,6 @@ def get_words(infile,what,prompt):
|
|||
return _get_words_from_user(prompt)
|
||||
|
||||
def remove_comments(lines):
|
||||
import re
|
||||
# re.sub(pattern, repl, string, count=0, flags=0)
|
||||
ret = []
|
||||
for i in lines:
|
||||
|
|
@ -573,6 +573,11 @@ def get_lines_from_file(infile,what="",trim_comments=False):
|
|||
return remove_comments(lines) if trim_comments else lines
|
||||
|
||||
|
||||
def get_data_from_user(what="data",silent=False):
|
||||
data = my_raw_input("Enter %s: " % what, echo=opt.echo_passphrase)
|
||||
if opt.debug: Msg("User input: [%s]" % data)
|
||||
return data
|
||||
|
||||
def get_data_from_file(infile,what="data",dash=False,silent=False):
|
||||
if dash and infile == "-": return sys.stdin.read()
|
||||
if not silent:
|
||||
|
|
@ -595,7 +600,7 @@ def get_seed_from_seed_data(words):
|
|||
chk = make_chksum_6(seed_b58)
|
||||
vmsg_r("Validating %s checksum..." % g.seed_ext)
|
||||
|
||||
if compare_checksums(chk, "from seed", stored_chk, "from input"):
|
||||
if compare_chksums(chk, "seed", stored_chk, "input",die=False):
|
||||
seed = b58decode_pad(seed_b58)
|
||||
if seed == False:
|
||||
msg("Invalid b58 number: %s" % val)
|
||||
|
|
@ -639,7 +644,6 @@ def get_bitcoind_passphrase(prompt):
|
|||
|
||||
def check_data_fits_file_at_offset(fname,offset,dlen,action):
|
||||
# TODO: Check for Windows
|
||||
import os, stat
|
||||
if stat.S_ISBLK(os.stat(fname).st_mode):
|
||||
fd = os.open(fname, os.O_RDONLY)
|
||||
fsize = os.lseek(fd, 0, os.SEEK_END)
|
||||
|
|
@ -656,9 +660,9 @@ def check_data_fits_file_at_offset(fname,offset,dlen,action):
|
|||
|
||||
from mmgen.term import kb_hold_protect,get_char
|
||||
|
||||
def get_hash_preset_from_user(hp='3',what="data"):
|
||||
p = "Enter hash preset for %s, or ENTER to accept the default ('%s'): " \
|
||||
% (what,hp)
|
||||
def get_hash_preset_from_user(hp=g.hash_preset,what="data"):
|
||||
p = """Enter hash preset for %s,
|
||||
or hit ENTER to accept the default value ('%s'): """ % (what,hp)
|
||||
while True:
|
||||
ret = my_raw_input(p)
|
||||
if ret:
|
||||
|
|
@ -721,3 +725,23 @@ def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
|
|||
|
||||
if verbose: msg("\nInvalid reply")
|
||||
else: msg_r("\r")
|
||||
|
||||
|
||||
def do_license_msg(immed=False):
|
||||
|
||||
from mmgen.license import gpl
|
||||
if opt.quiet or g.no_license: return
|
||||
|
||||
msg(gpl['warning'])
|
||||
prompt = "%s " % gpl['prompt'].strip()
|
||||
|
||||
while True:
|
||||
reply = get_char(prompt, immed_chars="wc" if immed else "")
|
||||
if reply == 'w':
|
||||
from mmgen.term import do_pager
|
||||
do_pager(gpl['conditions'])
|
||||
elif reply == 'c':
|
||||
msg(""); break
|
||||
else:
|
||||
msg_r("\r")
|
||||
msg("")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue