The original implementation of mmgen-passgen, using sha256x10(seed+id_str)
Superseded by the current implementation using sha256x10(hmac(seed,id_str,hashlib.sha256))
This commit is contained in:
parent
0ab39e4a22
commit
85cf5b3cbb
19 changed files with 697 additions and 301 deletions
25
mmgen-passgen
Executable file
25
mmgen-passgen
Executable file
|
|
@ -0,0 +1,25 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2017 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/>.
|
||||
|
||||
"""
|
||||
mmgen-passgen: Generate a series or range of passwords from an MMGen
|
||||
deterministic wallet
|
||||
"""
|
||||
|
||||
from mmgen.main import launch
|
||||
launch("passgen")
|
||||
249
mmgen/addr.py
249
mmgen/addr.py
|
|
@ -45,7 +45,7 @@ package on your system or specify the secp256k1 library.
|
|||
def _test_for_secp256k1(silent=False):
|
||||
no_secp256k1_errmsg = """
|
||||
secp256k1 library unavailable. Will use '{kconv}', or failing that, the (slow)
|
||||
internal ECDSA library for address generation.
|
||||
native Python ECDSA library for address generation.
|
||||
""".format(kconv=g.keyconv_exec)
|
||||
try:
|
||||
from mmgen.secp256k1 import priv2pub
|
||||
|
|
@ -94,7 +94,7 @@ def _keygen_selector(generator=None):
|
|||
else:
|
||||
if opt.key_generator == 3 and _test_for_secp256k1(): return 2
|
||||
elif opt.key_generator in (2,3) and _test_for_keyconv(): return 1
|
||||
msg('Using (slow) internal ECDSA library for address generation')
|
||||
msg('Using (slow) native Python ECDSA library for address generation')
|
||||
return 0
|
||||
|
||||
def get_wif2addr_f(generator=None):
|
||||
|
|
@ -107,21 +107,22 @@ def get_privhex2addr_f(generator=None):
|
|||
|
||||
class AddrListEntry(MMGenListItem):
|
||||
attrs = 'idx','addr','label','wif','sec'
|
||||
label = MMGenListItemAttr('label','MMGenAddrLabel')
|
||||
idx = MMGenListItemAttr('idx','AddrIdx')
|
||||
|
||||
class AddrListChksum(str,Hilite):
|
||||
color = 'pink'
|
||||
trunc_ok = False
|
||||
|
||||
def __new__(cls,addrlist):
|
||||
lines=[' '.join([str(e.idx),e.addr]+([e.wif] if addrlist.has_keys else []))
|
||||
for e in addrlist.data]
|
||||
els = ['addr','wif'] if addrlist.has_keys else ['sec'] if addrlist.gen_passwds else ['addr']
|
||||
lines = [' '.join([str(e.idx)] + [getattr(e,f) for f in els]) for e in addrlist.data]
|
||||
# print '[{}]'.format(' '.join(lines))
|
||||
return str.__new__(cls,make_chksum_N(' '.join(lines), nchars=16, sep=True))
|
||||
|
||||
class AddrListID(str,Hilite):
|
||||
class AddrListID(unicode,Hilite):
|
||||
color = 'green'
|
||||
trunc_ok = False
|
||||
def __new__(cls,addrlist):
|
||||
def __new__(cls,addrlist,fmt_str=None):
|
||||
try: int(addrlist.data[0].idx)
|
||||
except:
|
||||
s = '(no idxs)'
|
||||
|
|
@ -136,8 +137,8 @@ class AddrListID(str,Hilite):
|
|||
if prev != ret[-1]: ret += '-', prev
|
||||
ret += ',', i
|
||||
prev = i
|
||||
s = ''.join([str(i) for i in ret])
|
||||
return str.__new__(cls,'%s[%s]' % (addrlist.seed_id,s))
|
||||
s = ''.join([unicode(i) for i in ret])
|
||||
return unicode.__new__(cls,fmt_str.format(s) if fmt_str else '{}[{}]'.format(addrlist.seed_id,s))
|
||||
|
||||
class AddrList(MMGenObject): # Address info for a single seed ID
|
||||
msgs = {
|
||||
|
|
@ -158,11 +159,13 @@ Record this checksum: it will be used to verify the address file in the future
|
|||
Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
||||
""".strip().format(pnm=pnm)
|
||||
}
|
||||
main_key = 'addr'
|
||||
data_desc = 'address'
|
||||
file_desc = 'addresses'
|
||||
gen_desc = 'address'
|
||||
gen_desc = 'address'
|
||||
gen_desc_pl = 'es'
|
||||
gen_addrs = True
|
||||
gen_passwds = False
|
||||
gen_keys = False
|
||||
has_keys = False
|
||||
ext = 'addrs'
|
||||
|
|
@ -199,17 +202,15 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
self.fmt_data = ''
|
||||
self.id_str = None
|
||||
self.chksum = None
|
||||
self.id_str = AddrListID(self)
|
||||
|
||||
if type(self) == KeyList:
|
||||
self.id_str = AddrListID(self)
|
||||
return
|
||||
if type(self) == KeyList: return
|
||||
|
||||
if do_chksum:
|
||||
self.chksum = AddrListChksum(self)
|
||||
if chksum_only:
|
||||
Msg(self.chksum)
|
||||
else:
|
||||
self.id_str = AddrListID(self)
|
||||
qmsg('Checksum for %s data %s: %s' %
|
||||
(self.data_desc,self.id_str.hl(),self.chksum.hl()))
|
||||
qmsg(self.msgs[('check_chksum','record_chksum')[src=='gen']])
|
||||
|
|
@ -225,6 +226,8 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
self.seed_id = SeedID(seed=seed)
|
||||
seed = seed.get_data()
|
||||
|
||||
seed = self.cook_seed(seed)
|
||||
|
||||
if self.gen_addrs:
|
||||
privhex2addr_f = get_privhex2addr_f()
|
||||
|
||||
|
|
@ -238,7 +241,8 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
|
||||
pos += 1
|
||||
|
||||
qmsg_r('\rGenerating %s #%s (%s of %s)' % (self.gen_desc,num,pos,t_addrs))
|
||||
if not g.debug:
|
||||
qmsg_r('\rGenerating %s #%s (%s of %s)' % (self.gen_desc,num,pos,t_addrs))
|
||||
|
||||
e = AddrListEntry(idx=num)
|
||||
|
||||
|
|
@ -252,19 +256,27 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
e.wif = hex2wif(sec,compressed=False)
|
||||
if opt.b16: e.sec = sec
|
||||
|
||||
if self.gen_passwds:
|
||||
e.sec = self.make_passwd(sec)
|
||||
dmsg('Key {:>03}: {}'.format(pos,sec))
|
||||
|
||||
out.append(e)
|
||||
|
||||
qmsg('\r%s: %s %s%s generated%s' % (
|
||||
self.seed_id.hl(),t_addrs,self.gen_desc,suf(t_addrs,self.gen_desc_pl),' '*15))
|
||||
return out
|
||||
|
||||
def encrypt(self):
|
||||
def chk_addr_or_pw(self,addr): return is_btc_addr(addr)
|
||||
|
||||
def cook_seed(self,seed): return seed
|
||||
|
||||
def encrypt(self,desc='new key list'):
|
||||
from mmgen.crypto import mmgen_encrypt
|
||||
self.fmt_data = mmgen_encrypt(self.fmt_data,'new key list','')
|
||||
self.fmt_data = mmgen_encrypt(self.fmt_data.encode('utf8'),desc,'')
|
||||
self.ext += '.'+g.mmenc_ext
|
||||
|
||||
def write_to_file(self,ask_tty=True,ask_write_default_yes=False,binary=False):
|
||||
fn = '{}.{}'.format(self.id_str,self.ext)
|
||||
fn = u'{}.{}'.format(self.id_str,self.ext)
|
||||
ask_tty = self.has_keys and not opt.quiet
|
||||
write_data_to_file(fn,self.fmt_data,self.file_desc,ask_tty=ask_tty,binary=binary)
|
||||
|
||||
|
|
@ -359,23 +371,31 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
if not getattr(e,key):
|
||||
die(3,'missing %s in addr data' % desc)
|
||||
|
||||
if type(self) != KeyList: check_attrs('addr','addresses')
|
||||
if type(self) not in (KeyList,PasswordList): check_attrs('addr','addresses')
|
||||
|
||||
if self.has_keys:
|
||||
if opt.b16: check_attrs('sec','hex keys')
|
||||
check_attrs('wif','wif keys')
|
||||
|
||||
out = [self.msgs['file_header']+'\n']
|
||||
if self.chksum:
|
||||
out.append('# {} data checksum for {}: {}'.format(
|
||||
self.data_desc.capitalize(),self.id_str,self.chksum))
|
||||
out.append(u'# {} data checksum for {}: {}'.format(
|
||||
capfirst(self.data_desc),self.id_str,self.chksum))
|
||||
out.append('# Record this value to a secure location.\n')
|
||||
out.append('%s {' % self.seed_id)
|
||||
|
||||
if type(self) == PasswordList:
|
||||
out.append(u'{} {} {}:{} {{'.format(
|
||||
self.seed_id,self.pw_id_str,self.pw_fmt,self.pw_len))
|
||||
else:
|
||||
out.append('{} {{'.format(self.seed_id))
|
||||
|
||||
fs = ' {:<%s} {:<34}{}' % len(str(self.data[-1].idx))
|
||||
for e in self.data:
|
||||
c = ' '+e.label if enable_comments and e.label else ''
|
||||
if type(self) == KeyList:
|
||||
out.append(fs.format(e.idx, 'wif: '+e.wif,c))
|
||||
elif type(self) == PasswordList:
|
||||
out.append(fs.format(e.idx, e.sec, c))
|
||||
else: # First line with idx
|
||||
out.append(fs.format(e.idx, e.addr,c))
|
||||
if self.has_keys:
|
||||
|
|
@ -391,18 +411,20 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
return 'Key-address file has odd number of lines'
|
||||
|
||||
ret = []
|
||||
|
||||
while lines:
|
||||
l = lines.pop(0)
|
||||
d = l.split(None,2)
|
||||
|
||||
if not is_mmgen_idx(d[0]):
|
||||
return "'%s': invalid address num. in line: '%s'" % (d[0],l)
|
||||
if not is_btc_addr(d[1]):
|
||||
return "'%s': invalid Bitcoin address" % d[1]
|
||||
|
||||
if not self.chk_addr_or_pw(d[1]):
|
||||
return "'{}': invalid {}".format(d[1],self.data_desc)
|
||||
|
||||
if len(d) != 3: d.append('')
|
||||
|
||||
a = AddrListEntry(idx=int(d[0]),addr=d[1],label=d[2])
|
||||
a = AddrListEntry(**{'idx':int(d[0]),self.main_key:d[1],'label':d[2]})
|
||||
|
||||
if self.has_keys:
|
||||
l = lines.pop(0)
|
||||
|
|
@ -430,30 +452,40 @@ Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
|||
|
||||
def parse_file(self,fn,buf=[],exit_on_error=True):
|
||||
|
||||
def do_error(msg):
|
||||
if exit_on_error: die(3,msg)
|
||||
msg(msg)
|
||||
return False
|
||||
|
||||
lines = get_lines_from_file(fn,self.data_desc+' data',trim_comments=True)
|
||||
|
||||
try:
|
||||
sid,obrace = lines[0].split()
|
||||
except:
|
||||
errmsg = "Invalid first line: '%s'" % lines[0]
|
||||
else:
|
||||
cbrace = lines[-1]
|
||||
if obrace != '{':
|
||||
errmsg = "'%s': invalid first line" % lines[0]
|
||||
elif cbrace != '}':
|
||||
errmsg = "'%s': invalid last line" % cbrace
|
||||
elif not is_mmgen_seed_id(sid):
|
||||
errmsg = "'%s': invalid Seed ID" % sid
|
||||
else:
|
||||
ret = self.parse_file_body(lines[1:-1])
|
||||
if type(ret) == list:
|
||||
return sid,ret
|
||||
else:
|
||||
errmsg = ret
|
||||
if len(lines) < 3:
|
||||
return do_error("Too few lines in address file (%s)" % len(lines))
|
||||
|
||||
if exit_on_error: die(3,errmsg)
|
||||
msg(errmsg)
|
||||
return False
|
||||
ls = lines[0].split()
|
||||
ls_len = (2,4)[type(self)==PasswordList]
|
||||
if len(ls) != ls_len:
|
||||
return do_error("Invalid first line for {} file: '{}'".format(self.gen_desc,lines[0]))
|
||||
if ls[-1] != '{':
|
||||
return do_error("'%s': invalid first line" % ls)
|
||||
if lines[-1] != '}':
|
||||
return do_error("'%s': invalid last line" % lines[-1])
|
||||
if not is_mmgen_seed_id(ls[0]):
|
||||
return do_error("'%s': invalid Seed ID" % ls[0])
|
||||
|
||||
if type(self) == PasswordList:
|
||||
self.pw_id_str = MMGenPWIDString(ls[1])
|
||||
ss = ls[2].split(':')
|
||||
if len(ss) != 2:
|
||||
return do_error("'%s': invalid password length specifier (must contain colon)" % ls[2])
|
||||
self.set_pw_fmt(ss[0])
|
||||
self.set_pw_len(ss[1])
|
||||
|
||||
ret = self.parse_file_body(lines[1:-1])
|
||||
if type(ret) != list:
|
||||
return do_error(ret)
|
||||
|
||||
return ls[0],ret
|
||||
|
||||
class KeyAddrList(AddrList):
|
||||
data_desc = 'key-address'
|
||||
|
|
@ -483,6 +515,131 @@ class KeyList(AddrList):
|
|||
has_keys = True
|
||||
ext = 'keys'
|
||||
|
||||
class PasswordList(AddrList):
|
||||
msgs = {
|
||||
'file_header': """
|
||||
# {pnm} password file
|
||||
#
|
||||
# This file is editable.
|
||||
# Everything following a hash symbol '#' is a comment and ignored by {pnm}.
|
||||
# A text label of {n} characters or less may be added to the right of each
|
||||
# password. The label may contain any printable ASCII symbol.
|
||||
#
|
||||
""".strip().format(n=MMGenAddrLabel.max_len,pnm=pnm),
|
||||
'record_chksum': """
|
||||
Record this checksum: it will be used to verify the password file in the future
|
||||
""".strip()
|
||||
}
|
||||
main_key = 'sec'
|
||||
data_desc = 'password'
|
||||
file_desc = 'passwords'
|
||||
gen_desc = 'password'
|
||||
gen_desc_pl = 's'
|
||||
gen_addrs = False
|
||||
gen_keys = False
|
||||
gen_passwds = True
|
||||
has_keys = False
|
||||
ext = 'pws'
|
||||
pw_len = None
|
||||
pw_fmt = None
|
||||
pw_info = {
|
||||
'base58': { 'min_len': 8 , 'max_len': 36 ,'dfl_len': 20, 'desc': 'base-58 password' },
|
||||
'base32': { 'min_len': 10 ,'max_len': 42 ,'dfl_len': 24, 'desc': 'base-32 password' }
|
||||
}
|
||||
cook_hash_rounds = 10 # not too many rounds, so hand decoding can still be feasible
|
||||
|
||||
def __init__(self,
|
||||
seed=None,
|
||||
addr_idxs=None,
|
||||
pw_id_str=None,
|
||||
pw_len=None,
|
||||
infile=None,
|
||||
chksum_only=False,
|
||||
pw_fmt=None,
|
||||
chk_params_only=False
|
||||
):
|
||||
|
||||
self.update_msgs()
|
||||
|
||||
if infile:
|
||||
(self.seed_id,self.data) = self.parse_file(infile) # sets self.pw_id_str,self.pw_fmt,self.pw_len
|
||||
else:
|
||||
for k in seed,addr_idxs: assert chk_params_only or k
|
||||
for k in pw_id_str,pw_fmt: assert k
|
||||
self.pw_id_str = MMGenPWIDString(pw_id_str)
|
||||
self.set_pw_fmt(pw_fmt)
|
||||
self.set_pw_len(pw_len)
|
||||
if chk_params_only: return
|
||||
self.seed_id = seed.sid
|
||||
self.data = self.generate(seed,addr_idxs)
|
||||
|
||||
self.num_addrs = len(self.data)
|
||||
self.fmt_data = ''
|
||||
self.chksum = AddrListChksum(self)
|
||||
|
||||
if chksum_only:
|
||||
Msg(self.chksum)
|
||||
else:
|
||||
self.id_str = AddrListID(self,fmt_str=u'{}-{}-{}-{}[{{}}]'.format(
|
||||
self.seed_id,self.pw_id_str,self.pw_fmt,self.pw_len))
|
||||
qmsg(u'Checksum for {} data {}: {}'.format(self.data_desc,self.id_str.hl(),self.chksum.hl()))
|
||||
qmsg(self.msgs[('record_chksum','check_chksum')[bool(infile)]])
|
||||
|
||||
def set_pw_fmt(self,pw_fmt):
|
||||
assert pw_fmt in self.pw_info
|
||||
self.pw_fmt = pw_fmt
|
||||
|
||||
def chk_pw_len(self,passwd=None):
|
||||
if passwd is None:
|
||||
assert self.pw_len
|
||||
pw_len = self.pw_len
|
||||
fs = '{l}: invalid user-requested length for {b} ({c}{m})'
|
||||
else:
|
||||
pw_len = len(passwd)
|
||||
fs = '{pw}: {b} has invalid length {l} ({c}{m} characters)'
|
||||
d = self.pw_info[self.pw_fmt]
|
||||
if pw_len > d['max_len']:
|
||||
die(2,fs.format(l=pw_len,b=d['desc'],c='>',m=d['max_len'],pw=passwd))
|
||||
elif pw_len < d['min_len']:
|
||||
die(2,fs.format(l=pw_len,b=d['desc'],c='<',m=d['min_len'],pw=passwd))
|
||||
|
||||
def set_pw_len(self,pw_len):
|
||||
assert self.pw_fmt in self.pw_info
|
||||
d = self.pw_info[self.pw_fmt]
|
||||
|
||||
if pw_len is None:
|
||||
self.pw_len = d['dfl_len']
|
||||
return
|
||||
|
||||
if not is_int(pw_len):
|
||||
die(2,"'{}': invalid user-requested password length (not an integer)".format(pw_len,d['desc']))
|
||||
self.pw_len = int(pw_len)
|
||||
self.chk_pw_len()
|
||||
|
||||
def make_passwd(self,hex_sec):
|
||||
assert self.pw_fmt in self.pw_info
|
||||
from mmgen.bitcoin import b58a
|
||||
alpha,base = ((b58a,58),(b32a,32))[self.pw_fmt=='base32']
|
||||
# we take least significant part
|
||||
return ''.join(baseconv.fromhex(base,hex_sec,alpha,pad=self.pw_len))[-self.pw_len:]
|
||||
|
||||
def chk_addr_or_pw(self,pw):
|
||||
if not (is_b58_str,is_b32_str)[self.pw_fmt=='base32'](pw):
|
||||
msg('Password is not a valid {} string'.format(self.pw_fmt))
|
||||
return False
|
||||
if len(pw) != self.pw_len:
|
||||
msg('Password has incorrect length ({} != {})'.format(len(pw),self.pw_len))
|
||||
return False
|
||||
return True
|
||||
|
||||
def cook_seed(self,seed):
|
||||
from mmgen.crypto import sha256_rounds
|
||||
# Changing either pw_fmt or pw_len will cause a different, unrelated set of passwords to
|
||||
# be generated: this is what we want
|
||||
cseed = '{}{}:{}:{}'.format(seed,self.pw_fmt,self.pw_len,self.pw_id_str.encode('utf8'))
|
||||
dmsg('Cooked seed: {}\nSeed len: {}'.format(repr(cseed),len(cseed)))
|
||||
return sha256_rounds(cseed,self.cook_hash_rounds)
|
||||
|
||||
|
||||
class AddrData(MMGenObject):
|
||||
msgs = {
|
||||
|
|
|
|||
|
|
@ -53,6 +53,12 @@ keystrokes will also be used as a source of randomness.
|
|||
# """.strip(),
|
||||
}
|
||||
|
||||
def sha256_rounds(s,n):
|
||||
assert is_int(n) and n > 0
|
||||
for i in range(n):
|
||||
s = sha256(s).digest()
|
||||
return s
|
||||
|
||||
def encrypt_seed(seed, key):
|
||||
return encrypt_data(seed, key, iv=1, desc='seed')
|
||||
|
||||
|
|
@ -237,7 +243,7 @@ def mmgen_decrypt(data,desc='data',hash_preset=''):
|
|||
m = ('user-requested','default')[hp=='3']
|
||||
qmsg("Using %s hash preset of '%s'" % (m,hp))
|
||||
passwd = get_mmgen_passphrase(desc)
|
||||
key = make_key(passwd, salt, hp)
|
||||
key = make_key(passwd,salt,hp)
|
||||
dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), desc)
|
||||
if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
|
||||
vmsg('OK')
|
||||
|
|
|
|||
|
|
@ -74,13 +74,13 @@ def find_files_in_dir(ftype,fdir,no_dups=False):
|
|||
die(3,"'{}': not a recognized file type".format(ftype))
|
||||
|
||||
try: dirlist = os.listdir(fdir)
|
||||
except: die(3,"ERROR: unable to read directory '{}'".format(fdir))
|
||||
except: die(3,u"ERROR: unable to read directory '{}'".format(fdir))
|
||||
|
||||
matches = [l for l in dirlist if l[-len(ftype.ext)-1:]=='.'+ftype.ext]
|
||||
|
||||
if no_dups:
|
||||
if len(matches) > 1:
|
||||
die(1,"ERROR: more than one {} file in directory '{}'".format(ftype.__name__,fdir))
|
||||
die(1,u"ERROR: more than one {} file in directory '{}'".format(ftype.__name__,fdir))
|
||||
return os.path.join(fdir,matches[0]) if len(matches) else None
|
||||
else:
|
||||
return [os.path.join(fdir,m) for m in matches]
|
||||
|
|
|
|||
|
|
@ -22,6 +22,16 @@ main.py - Script launcher for the MMGen suite
|
|||
|
||||
def launch(what):
|
||||
|
||||
def my_dec(a):
|
||||
try:
|
||||
return a.decode('utf8')
|
||||
except:
|
||||
sys.stderr.write("Argument '{}' is not a valid UTF-8 string".format(a))
|
||||
sys.exit(2)
|
||||
|
||||
import sys
|
||||
sys.argv = [my_dec(a) for a in sys.argv]
|
||||
|
||||
if what in ('walletgen','walletchk','walletconv','passchg'):
|
||||
what = 'wallet'
|
||||
if what == 'keygen': what = 'addrgen'
|
||||
|
|
@ -30,7 +40,7 @@ def launch(what):
|
|||
except: # Windows
|
||||
__import__('mmgen.main_' + what)
|
||||
else:
|
||||
import sys,os,atexit
|
||||
import os,atexit
|
||||
if sys.stdin.isatty():
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
|
|
|
|||
|
|
@ -28,23 +28,24 @@ from mmgen.seed import SeedSource
|
|||
|
||||
if sys.argv[0].split('-')[-1] == 'keygen':
|
||||
gen_what = 'keys'
|
||||
gen_desc = 'secret keys'
|
||||
opt_filter = None
|
||||
note1 = """
|
||||
By default, both addresses and secret keys are generated.
|
||||
""".strip()
|
||||
note2 = 'By default, both addresses and secret keys are generated.\n\n'
|
||||
else:
|
||||
gen_what = 'addresses'
|
||||
gen_desc = 'addresses'
|
||||
opt_filter = 'hbcdeiHOKlpzPqrSv-'
|
||||
note1 = """
|
||||
If available, the external 'keyconv' program will be used for address
|
||||
generation.
|
||||
note2 = ''
|
||||
note1 = """
|
||||
If available, the secp256k1 library will be used for address generation.
|
||||
""".strip()
|
||||
|
||||
|
||||
opts_data = {
|
||||
'sets': [('print_checksum',True,'quiet',True)],
|
||||
'desc': """Generate a range or list of {what} from an {pnm} wallet,
|
||||
mnemonic, seed or password""".format(what=gen_what,pnm=g.proj_name),
|
||||
'usage':'[opts] [infile] <range or list of address indexes>',
|
||||
'desc': """Generate a range or list of {desc} from an {pnm} wallet,
|
||||
mnemonic, seed or brainwallet""".format(desc=gen_desc,pnm=g.proj_name),
|
||||
'usage':'[opts] [seed source] <index list or range(s)>',
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
|
|
@ -80,9 +81,16 @@ opts_data = {
|
|||
),
|
||||
'notes': """
|
||||
|
||||
Address indexes are given in a comma-separated list and/or hyphen-separated ranges.
|
||||
|
||||
{n}
|
||||
NOTES FOR THIS COMMAND
|
||||
|
||||
Address indexes are given as a comma-separated list and/or hyphen-separated
|
||||
range(s).
|
||||
|
||||
{n2}{n1}
|
||||
|
||||
|
||||
NOTES FOR ALL GENERATOR COMMANDS
|
||||
|
||||
{o.pw_note}
|
||||
|
||||
|
|
@ -91,7 +99,7 @@ Address indexes are given in a comma-separated list and/or hyphen-separated rang
|
|||
FMT CODES:
|
||||
{f}
|
||||
""".format(
|
||||
n=note1,
|
||||
n1=note1,n2=note2,
|
||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||
o=opts
|
||||
)
|
||||
|
|
|
|||
138
mmgen/main_passgen.py
Executable file
138
mmgen/main_passgen.py
Executable file
|
|
@ -0,0 +1,138 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C)2013-2017 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/>.
|
||||
|
||||
"""
|
||||
mmgen-passgen: Generate a series or range of passwords from an MMGen
|
||||
deterministic wallet
|
||||
"""
|
||||
|
||||
from mmgen.common import *
|
||||
from mmgen.crypto import *
|
||||
from mmgen.addr import PasswordList,AddrIdxList
|
||||
from mmgen.seed import SeedSource
|
||||
from mmgen.obj import MMGenPWIDString
|
||||
|
||||
opts_data = {
|
||||
'sets': [('print_checksum',True,'quiet',True)],
|
||||
'desc': """Generate a range or list of passwords from an {pnm} wallet,
|
||||
mnemonic, seed or brainwallet for the given ID string""".format(pnm=g.proj_name),
|
||||
'usage':'[opts] [seed source] <ID string> <index list or range(s)>',
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
--, --longhelp Print help message for long options (common options)
|
||||
-b, --base32 Generate passwords in Base32 format instead of Base58
|
||||
-d, --outdir= d Output files to directory 'd' instead of working dir
|
||||
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
|
||||
-i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below)
|
||||
-H, --hidden-incog-input-params=f,o Read hidden incognito data from file
|
||||
'f' at offset 'o' (comma-separated)
|
||||
-O, --old-incog-fmt Specify old-format incognito input
|
||||
-L, --passwd-len= l Specify length of generated passwords
|
||||
(default: {p} chars [base58], {q} chars [base32])
|
||||
-l, --seed-len= l Specify wallet seed length of 'l' bits. This option
|
||||
is required only for brainwallet and incognito inputs
|
||||
with non-standard (< {g.seed_len}-bit) seed lengths
|
||||
-p, --hash-preset= p Use the scrypt hash parameters defined by preset 'p'
|
||||
for password hashing (default: '{g.hash_preset}')
|
||||
-z, --show-hash-presets Show information on available hash presets
|
||||
-P, --passwd-file= f Get wallet passphrase from file 'f'
|
||||
-q, --quiet Produce quieter output; suppress some warnings
|
||||
-r, --usr-randchars=n Get 'n' characters of additional randomness from user
|
||||
(min={g.min_urandchars}, max={g.max_urandchars}, default={g.usr_randchars})
|
||||
-S, --stdout Print passwords to stdout
|
||||
-v, --verbose Produce more verbose output
|
||||
""".format(
|
||||
seed_lens=', '.join([str(i) for i in g.seed_lens]),
|
||||
pnm=g.proj_name,
|
||||
kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
|
||||
g=g,
|
||||
p=PasswordList.pw_info['base58']['dfl_len'],
|
||||
q=PasswordList.pw_info['base32']['dfl_len']
|
||||
),
|
||||
'notes': """
|
||||
|
||||
|
||||
NOTES FOR THIS COMMAND
|
||||
|
||||
ID string must be a valid UTF-8 string not longer than {ml} characters and
|
||||
not containing the symbols '{fs}'.
|
||||
|
||||
Password indexes are given as a comma-separated list and/or hyphen-separated
|
||||
range(s).
|
||||
|
||||
Changing either the password format (base32,base58) or length alters the seed
|
||||
and thus generates a completely new set of passwords.
|
||||
|
||||
EXAMPLE:
|
||||
Generate ten base58 passwords of length {dfl58} for Alice's email account:
|
||||
{g.prog_name} alice@nowhere.com 1-10
|
||||
|
||||
Generate ten base58 passwords of length 16 for Alice's email account:
|
||||
{g.prog_name} -L16 alice@nowhere.com 1-10
|
||||
|
||||
Generate ten base32 passwords of length {dfl32} for Alice's email account:
|
||||
{g.prog_name} -b alice@nowhere.com 1-10
|
||||
|
||||
The three sets of passwords are completely unrelated to each other, so
|
||||
Alice doesn't need to worry about password reuse.
|
||||
|
||||
|
||||
NOTES FOR ALL GENERATOR COMMANDS
|
||||
|
||||
{o.pw_note}
|
||||
|
||||
{o.bw_note}
|
||||
|
||||
FMT CODES:
|
||||
{f}
|
||||
""".format(
|
||||
f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
|
||||
o=opts,g=g,
|
||||
ml=MMGenPWIDString.max_len,
|
||||
dfl58=PasswordList.pw_info['base58']['dfl_len'],
|
||||
dfl32=PasswordList.pw_info['base32']['dfl_len'],
|
||||
fs="', '".join(MMGenPWIDString.forbidden)
|
||||
)
|
||||
}
|
||||
|
||||
cmd_args = opts.init(opts_data,add_opts=['b16'])
|
||||
|
||||
if len(cmd_args) < 2: opts.usage()
|
||||
|
||||
idxs = AddrIdxList(fmt_str=cmd_args.pop())
|
||||
|
||||
pw_id_str = cmd_args.pop()
|
||||
|
||||
sf = get_seed_file(cmd_args,1)
|
||||
|
||||
pw_fmt = ('base58','base32')[bool(opt.base32)]
|
||||
|
||||
PasswordList(pw_id_str=pw_id_str,pw_len=opt.passwd_len,pw_fmt=pw_fmt,chk_params_only=True)
|
||||
do_license_msg()
|
||||
|
||||
ss = SeedSource(sf)
|
||||
|
||||
al = PasswordList(seed=ss.seed,addr_idxs=idxs,pw_id_str=pw_id_str,pw_len=opt.passwd_len,pw_fmt=pw_fmt)
|
||||
|
||||
al.format()
|
||||
|
||||
if keypress_confirm('Encrypt password list?'):
|
||||
al.encrypt(desc='password list')
|
||||
al.write_to_file(binary=True)
|
||||
else:
|
||||
al.write_to_file()
|
||||
134
mmgen/obj.py
134
mmgen/obj.py
|
|
@ -17,7 +17,7 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
obj.py: The MMGenObject class and methods
|
||||
obj.py: MMGen native classes
|
||||
"""
|
||||
|
||||
from decimal import *
|
||||
|
|
@ -26,72 +26,39 @@ 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) in (str,unicode):
|
||||
from string import printable
|
||||
if not (set(list(v)) <= set(list(printable))):
|
||||
vret = repr(v)
|
||||
# Pretty-print any object of type MMGenObject, recursing into sub-objects - WIP
|
||||
def pprint(self): print self.pformat()
|
||||
def pformat(self,lvl=0):
|
||||
def do_list(out,e,lvl=0):
|
||||
add_spc = False
|
||||
if e and type(e[0]) not in (str,unicode):
|
||||
out.append('\n')
|
||||
for i in e:
|
||||
if hasattr(i,'pformat'):
|
||||
out.append('{:>{l}}{}'.format('',i.pformat(lvl=lvl+1),l=(lvl+1)*8))
|
||||
elif type(i) in (str,unicode):
|
||||
add_spc = True
|
||||
out.append(u' {}'.format(repr(i)))
|
||||
elif type(i) == list:
|
||||
out.append(u'{:>{l}}{:16}'.format('','<'+type(i).__name__+'>',l=(lvl*8)+4))
|
||||
do_list(out,i,lvl=lvl)
|
||||
else:
|
||||
vret = fix_linebreaks(v,fixed_indent=0)
|
||||
elif type(v) in (int,long,BTCAmt):
|
||||
vret = str(v)
|
||||
elif type(v) == dict:
|
||||
sep = '\n{}{}'.format(indent,' '*4)
|
||||
cw = (max(len(k) for k in v) if v else 0) + 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 = (('(',')'),('[',']'))[type(v)==list]
|
||||
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.append(u'{:>{l}}{:16} {}\n'.format('','<'+type(i).__name__+'>',repr(i),l=(lvl*8)+8))
|
||||
if not e: out.append('{}\n'.format(repr(e)))
|
||||
if add_spc: out.append('\n')
|
||||
out = []
|
||||
def f(k): return k[:2] != '__'
|
||||
keys = filter(f, self.__dict__.keys())
|
||||
col_w = max(len(k) for k in keys) if keys else 1
|
||||
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,type(val)))
|
||||
lvl += 1
|
||||
out.append(unicode(getattr(self,k))+'\n')
|
||||
lvl -= 1
|
||||
out.append(u'<{}>\n'.format(type(self).__name__))
|
||||
d = self.__dict__
|
||||
for k in d:
|
||||
e = getattr(self,k)
|
||||
if type(e) == list:
|
||||
out.append(u'{:>{l}}{:<10} {:16}'.format('',k,'<'+type(e).__name__+'>',l=(lvl*8)+4))
|
||||
do_list(out,e,lvl=lvl)
|
||||
elif hasattr(e,'pformat') and type(e) != type:
|
||||
out.append(u'{:>{l}}{:10} {}'.format('',k,e.pformat(lvl=lvl+1),l=(lvl*8)+4))
|
||||
else:
|
||||
out.append(fs % (k, conv(val,col_w)))
|
||||
|
||||
return repr(self) + '\n ' + '\n '.join(out)
|
||||
out.append(u'{:>{l}}{:<10} {:16} {}\n'.format('',k,'<'+type(e).__name__+'>',repr(e),l=(lvl*8)+4))
|
||||
return ''.join(out)
|
||||
|
||||
# Descriptor: https://docs.python.org/2/howto/descriptor.html
|
||||
class MMGenListItemAttr(object):
|
||||
|
|
@ -124,7 +91,7 @@ class MMGenListItem(MMGenObject):
|
|||
"'{}': attribute '{}' in instance of class '{}' cannot be reassigned".format(
|
||||
val,attr,type(self).__name__)
|
||||
|
||||
attrs_base = ('attrs','attrs_priv','attrs_reassign','attrs_base','attr_error','set_error','__dict__')
|
||||
attrs_base = ('attrs','attrs_priv','attrs_reassign','attrs_base','attr_error','set_error','__dict__','pformat')
|
||||
|
||||
def __init__(self,*args,**kwargs):
|
||||
if args:
|
||||
|
|
@ -432,36 +399,55 @@ class BitcoinTxID(MMGenTxID):
|
|||
class MMGenLabel(unicode,Hilite,InitErrors):
|
||||
|
||||
color = 'pink'
|
||||
allowed = u''
|
||||
allowed = []
|
||||
forbidden = []
|
||||
max_len = 0
|
||||
min_len = 0
|
||||
desc = 'label'
|
||||
|
||||
def __new__(cls,s,on_fail='die',msg=None):
|
||||
cls.arg_chk(cls,on_fail)
|
||||
for k in cls.forbidden,cls.allowed:
|
||||
assert type(k) == list
|
||||
for ch in k: assert type(ch) == unicode and len(ch) == 1
|
||||
try:
|
||||
s = s.decode('utf8').strip()
|
||||
s = s.strip()
|
||||
if type(s) != unicode:
|
||||
s = s.decode('utf8')
|
||||
except:
|
||||
m = "'%s: value is not a valid UTF-8 string" % s
|
||||
m = "'%s': value is not a valid UTF-8 string" % s
|
||||
else:
|
||||
from mmgen.util import capfirst
|
||||
if len(s) > cls.max_len:
|
||||
m = '%s too long (>%s symbols)' % (cls.desc.capitalize(),cls.max_len)
|
||||
elif cls.allowed and not set(list(s)).issubset(set(list(cls.allowed))):
|
||||
m = '%s contains non-permitted symbols: %s' % (cls.desc.capitalize(),
|
||||
' '.join(set(list(s)) - set(list(cls.allowed))))
|
||||
m = u"'{}': {} too long (>{} symbols)".format(s,capfirst(cls.desc),cls.max_len)
|
||||
elif len(s) < cls.min_len:
|
||||
m = u"'{}': {} too short (<{} symbols)".format(s,capfirst(cls.desc),cls.min_len)
|
||||
elif cls.allowed and not set(list(s)).issubset(set(cls.allowed)):
|
||||
m = u"{} '{}' contains non-allowed symbols: {}".format(capfirst(cls.desc),s,
|
||||
' '.join(set(list(s)) - set(cls.allowed)))
|
||||
elif cls.forbidden and any([ch in s for ch in cls.forbidden]):
|
||||
m = u"{} '{}' contains one of these forbidden symbols: '{}'".format(capfirst(cls.desc),s,
|
||||
"', '".join(cls.forbidden))
|
||||
else:
|
||||
return unicode.__new__(cls,s)
|
||||
return cls.init_fail((msg+'\n' if msg else '') + m,on_fail)
|
||||
|
||||
class MMGenWalletLabel(MMGenLabel):
|
||||
max_len = 48
|
||||
allowed = [chr(i+32) for i in range(95)]
|
||||
allowed = [unichr(i+32) for i in range(95)]
|
||||
desc = 'wallet label'
|
||||
|
||||
class MMGenAddrLabel(MMGenLabel):
|
||||
max_len = 32
|
||||
allowed = [chr(i+32) for i in range(95)]
|
||||
allowed = [unichr(i+32) for i in range(95)]
|
||||
desc = 'address label'
|
||||
|
||||
class MMGenTXLabel(MMGenLabel):
|
||||
max_len = 72
|
||||
desc = 'transaction label'
|
||||
|
||||
class MMGenPWIDString(MMGenLabel):
|
||||
max_len = 256
|
||||
min_len = 1
|
||||
desc = 'password ID string'
|
||||
forbidden = list(u' :/\\')
|
||||
|
|
|
|||
|
|
@ -104,7 +104,7 @@ class BitcoinRPCConnection(object):
|
|||
'Authorization': 'Basic {}'.format(base64.b64encode(self.auth_str))
|
||||
})
|
||||
except Exception as e:
|
||||
return die_maybe(None,2,'%s\nUnable to connect to bitcoind' % e)
|
||||
return die_maybe(None,2,'{}\nUnable to connect to bitcoind at {}:{}'.format(e,self.host,self.port))
|
||||
|
||||
r = hc.getresponse() # returns HTTPResponse instance
|
||||
|
||||
|
|
|
|||
|
|
@ -370,38 +370,15 @@ class Mnemonic (SeedSourceUnenc):
|
|||
@staticmethod
|
||||
def _hex2mn_pad(hexnum): return len(hexnum) * 3 / 8
|
||||
|
||||
@staticmethod
|
||||
def baseNtohex(base,words_arg,wl,pad=0): # accepts both string and list input
|
||||
words = words_arg
|
||||
if type(words) not in (list,tuple):
|
||||
words = tuple(words.strip())
|
||||
if not set(words).issubset(set(wl)):
|
||||
die(2,'{} is not in base-{} format'.format(repr(words_arg),base))
|
||||
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
|
||||
|
||||
@staticmethod
|
||||
def hextobaseN(base,hexnum,wl,pad=0):
|
||||
hexnum = hexnum.strip()
|
||||
if not is_hexstring(hexnum):
|
||||
die(2,"'%s': not a hexadecimal number" % hexnum)
|
||||
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]]
|
||||
|
||||
@classmethod
|
||||
def hex2mn(cls,hexnum,wordlist):
|
||||
wl = cls.get_wordlist(wordlist)
|
||||
return cls.hextobaseN(cls.mn_base,hexnum,wl,cls._hex2mn_pad(hexnum))
|
||||
return baseconv.fromhex(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))
|
||||
return baseconv.tohex(cls.mn_base,mn,wl,cls._mn2hex_pad(mn))
|
||||
|
||||
@classmethod
|
||||
def get_wordlist(cls,wordlist=None):
|
||||
|
|
@ -433,9 +410,9 @@ class Mnemonic (SeedSourceUnenc):
|
|||
def _format(self):
|
||||
wl = self.get_wordlist()
|
||||
seed_hex = self.seed.hexdata
|
||||
mn = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
|
||||
mn = baseconv.fromhex(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
|
||||
|
||||
ret = self.baseNtohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
|
||||
ret = baseconv.tohex(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')
|
||||
|
|
@ -458,9 +435,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 = baseconv.tohex(self.mn_base,mn,wl,self._mn2hex_pad(mn))
|
||||
|
||||
ret = self.hextobaseN(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
|
||||
ret = baseconv.fromhex(self.mn_base,seed_hex,wl,self._hex2mn_pad(seed_hex))
|
||||
|
||||
# Internal error, so just die
|
||||
compare_or_die(' '.join(ret),'recomputed mnemonic',
|
||||
|
|
@ -503,7 +480,7 @@ class SeedFile (SeedSourceUnenc):
|
|||
msg("'%s': invalid checksum format in %s" % (a, desc))
|
||||
return False
|
||||
|
||||
if not is_b58string(b):
|
||||
if not is_b58_str(b):
|
||||
msg("'%s': not a base 58 string, in %s" % (b, desc))
|
||||
return False
|
||||
|
||||
|
|
@ -557,7 +534,7 @@ class HexSeedFile (SeedSourceUnenc):
|
|||
msg("'%s': invalid checksum format in %s" % (chk, desc))
|
||||
return False
|
||||
|
||||
if not is_hexstring(hstr):
|
||||
if not is_hex_str(hstr):
|
||||
msg("'%s': not a hexadecimal string, in %s" % (hstr, desc))
|
||||
return False
|
||||
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ from collections import OrderedDict
|
|||
cmd_data = OrderedDict([
|
||||
('help', ['<tool command> [str]']),
|
||||
('usage', ['<tool command> [str]']),
|
||||
('strtob58', ['<string> [str-]']),
|
||||
('strtob58', ['<string> [str-]','pad [int=0]']),
|
||||
('b58tostr', ['<b58 number> [str-]']),
|
||||
('hextob58', ['<hex number> [str-]']),
|
||||
('b58tohex', ['<b58 number> [str-]']),
|
||||
('hextob58', ['<hex number> [str-]','pad [int=0]']),
|
||||
('b58tohex', ['<b58 number> [str-]','pad [int=0]']),
|
||||
('b58randenc', []),
|
||||
('b32tohex', ['<b32 num> [str-]']),
|
||||
('hextob32', ['<hex num> [str-]']),
|
||||
('b32tohex', ['<b32 num> [str-]','pad [int=0]']),
|
||||
('hextob32', ['<hex num> [str-]','pad [int=0]']),
|
||||
('randhex', ['nbytes [int=32]']),
|
||||
('id8', ['<infile> [str]']),
|
||||
('id6', ['<infile> [str]']),
|
||||
|
|
@ -80,6 +80,7 @@ cmd_data = OrderedDict([
|
|||
('remove_label', ['<{} address> [str]'.format(pnm)]),
|
||||
('addrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
|
||||
('keyaddrfile_chksum', ['<{} addr file> [str]'.format(pnm)]),
|
||||
('passwdfile_chksum', ['<{} password file> [str]'.format(pnm)]),
|
||||
('find_incog_data', ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]']),
|
||||
|
||||
('encrypt', ['<infile> [str]',"outfile [str='']","hash_preset [str='']"]),
|
||||
|
|
@ -137,6 +138,7 @@ cmd_help = """
|
|||
remove_label - remove descriptive label for {pnm} address in tracking wallet
|
||||
addrfile_chksum - compute checksum for {pnm} address file
|
||||
keyaddrfile_chksum - compute checksum for {pnm} key-address file
|
||||
passwdfile_chksum - compute checksum for {pnm} password file
|
||||
find_incog_data - Use an Incog ID to find hidden incognito wallet data
|
||||
id6 - generate 6-character {pnm} ID for a file (or stdin)
|
||||
id8 - generate 8-character {pnm} ID for a file (or stdin)
|
||||
|
|
@ -213,6 +215,7 @@ def process_args(prog_name, command, cmd_args):
|
|||
tool_usage(prog_name,command)
|
||||
|
||||
def conv_type(arg,arg_name,arg_type):
|
||||
if arg_type == 'str': arg_type = 'unicode'
|
||||
if arg_type == 'bool':
|
||||
if arg.lower() in ('true','yes','1','on'): arg = True
|
||||
elif arg.lower() in ('false','no','0','off'): arg = False
|
||||
|
|
@ -266,32 +269,6 @@ def unhexdump(infile):
|
|||
sys.stdout.write(decode_pretty_hexdump(
|
||||
get_data_from_file(infile,dash=True,silent=True)))
|
||||
|
||||
def strtob58(s):
|
||||
enc = mmb.b58encode(s)
|
||||
dec = mmb.b58decode(enc)
|
||||
print_convert_results(s,enc,dec,'str')
|
||||
|
||||
def hextob58(s,f_enc=mmb.b58encode, f_dec=mmb.b58decode):
|
||||
s = s.strip()
|
||||
enc = f_enc(ba.unhexlify(s))
|
||||
dec = ba.hexlify(f_dec(enc))
|
||||
print_convert_results(s,enc,dec,'hex')
|
||||
|
||||
def b58tohex(s,f_enc=mmb.b58decode, f_dec=mmb.b58encode):
|
||||
s = s.strip()
|
||||
tmp = f_enc(s)
|
||||
if tmp == False: die(1,"Unable to decode string '%s'" % s)
|
||||
enc = ba.hexlify(tmp)
|
||||
dec = f_dec(ba.unhexlify(enc))
|
||||
print_convert_results(s,enc,dec,'b58')
|
||||
|
||||
def b58tostr(s,f_enc=mmb.b58decode, f_dec=mmb.b58encode):
|
||||
s = s.strip()
|
||||
enc = f_enc(s)
|
||||
if enc == False: die(1,"Unable to decode string '%s'" % s)
|
||||
dec = f_dec(enc)
|
||||
print_convert_results(s,enc,dec,'b58')
|
||||
|
||||
def b58randenc():
|
||||
r = get_random(32)
|
||||
enc = mmb.b58encode(r)
|
||||
|
|
@ -331,7 +308,7 @@ def do_random_mn(nbytes,wordlist):
|
|||
Vmsg('Seed: %s' % hexrand)
|
||||
for wlname in ([wordlist],wordlists)[wordlist=='all']:
|
||||
if wordlist == 'all':
|
||||
Msg('%s mnemonic:' % (wlname.capitalize()))
|
||||
Msg('%s mnemonic:' % (capfirst(wlname)))
|
||||
mn = Mnemonic.hex2mn(hexrand,wordlist=wlname)
|
||||
Msg(' '.join(mn))
|
||||
|
||||
|
|
@ -340,20 +317,28 @@ def mn_rand192(wordlist=dfl_wordlist): do_random_mn(24,wordlist)
|
|||
def mn_rand256(wordlist=dfl_wordlist): do_random_mn(32,wordlist)
|
||||
|
||||
def hex2mn(s,wordlist=dfl_wordlist):
|
||||
mn = Mnemonic.hex2mn(s,wordlist)
|
||||
Msg(' '.join(mn))
|
||||
Msg(' '.join(Mnemonic.hex2mn(s,wordlist)))
|
||||
|
||||
def mn2hex(s,wordlist=dfl_wordlist):
|
||||
hexnum = Mnemonic.mn2hex(s.split(),wordlist)
|
||||
Msg(hexnum)
|
||||
Msg(Mnemonic.mn2hex(s.split(),wordlist))
|
||||
|
||||
def b32tohex(s):
|
||||
b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
Msg(Mnemonic.baseNtohex(32,s.upper(),b32a))
|
||||
def strtob58(s,pad=None):
|
||||
Msg(''.join(baseconv.fromhex(58,ba.hexlify(s),mmb.b58a,pad)))
|
||||
|
||||
def hextob32(s):
|
||||
b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
Msg(''.join(Mnemonic.hextobaseN(32,s,b32a)))
|
||||
def b58tostr(s):
|
||||
Msg(ba.unhexlify(baseconv.tohex(58,s,mmb.b58a)))
|
||||
|
||||
def b58tohex(s,pad=None):
|
||||
Msg(baseconv.tohex(58,s,mmb.b58a,pad))
|
||||
|
||||
def hextob58(s,pad=None):
|
||||
Msg(''.join(baseconv.fromhex(58,s,mmb.b58a,pad)))
|
||||
|
||||
def b32tohex(s,pad=None):
|
||||
Msg(baseconv.tohex(32,s.upper(),b32a,pad))
|
||||
|
||||
def hextob32(s,pad=None):
|
||||
Msg(''.join(baseconv.fromhex(32,s,b32a,pad)))
|
||||
|
||||
def mn_stats(wordlist=dfl_wordlist):
|
||||
Mnemonic.check_wordlist(wordlist)
|
||||
|
|
@ -506,6 +491,10 @@ def keyaddrfile_chksum(infile):
|
|||
from mmgen.addr import KeyAddrList
|
||||
KeyAddrList(infile,chksum_only=True)
|
||||
|
||||
def passwdfile_chksum(infile):
|
||||
from mmgen.addr import PasswordList
|
||||
PasswordList(infile=infile,chksum_only=True)
|
||||
|
||||
def hexreverse(s):
|
||||
Msg(ba.hexlify(ba.unhexlify(s.strip())[::-1]))
|
||||
|
||||
|
|
|
|||
|
|
@ -107,7 +107,7 @@ watch-only wallet using '{}-addrimport' and then re-run this program.
|
|||
|
||||
def sort_info(self,include_group=True):
|
||||
ret = ([],['Reverse'])[self.reverse]
|
||||
ret.append(self.sort_key.capitalize().replace('Mmid','MMGenID'))
|
||||
ret.append(capfirst(self.sort_key).replace('Mmid','MMGenID'))
|
||||
if include_group and self.group and (self.sort_key in ('addr','txid','mmid')):
|
||||
ret.append('Grouped')
|
||||
return ret
|
||||
|
|
|
|||
11
mmgen/tx.py
11
mmgen/tx.py
|
|
@ -31,10 +31,6 @@ def is_mmgen_idx(s): return AddrIdx(s,on_fail='silent')
|
|||
def is_mmgen_id(s): return MMGenID(s,on_fail='silent')
|
||||
def is_btc_addr(s): return BTCAddr(s,on_fail='silent')
|
||||
|
||||
def is_b58_str(s):
|
||||
from mmgen.bitcoin import b58a
|
||||
return set(list(s)) <= set(b58a)
|
||||
|
||||
def is_wif(s):
|
||||
if s == '': return False
|
||||
from mmgen.bitcoin import wif2hex
|
||||
|
|
@ -376,7 +372,7 @@ class MMGenTX(MMGenObject):
|
|||
self.view(pager=reply in 'Vv',terse=reply in 'Tt')
|
||||
|
||||
def view(self,pager=False,pause=True,terse=False):
|
||||
o = self.format_view(terse=terse)
|
||||
o = self.format_view(terse=terse).encode('utf8')
|
||||
if pager: do_pager(o)
|
||||
else:
|
||||
sys.stdout.write(o)
|
||||
|
|
@ -480,9 +476,8 @@ class MMGenTX(MMGenObject):
|
|||
ts = len(self.hex)/2 if self.hex else 'unknown'
|
||||
out += 'Transaction size: estimated - {}, actual - {}\n'.format(self.get_size(),ts)
|
||||
|
||||
# only tx label may contain non-ascii chars
|
||||
# encode() is necessary for test suite with PopenSpawn
|
||||
return out.encode('utf8')
|
||||
# TX label might contain non-ascii chars
|
||||
return out
|
||||
|
||||
def parse_tx_file(self,infile):
|
||||
|
||||
|
|
|
|||
129
mmgen/util.py
129
mmgen/util.py
|
|
@ -26,50 +26,42 @@ from binascii import hexlify,unhexlify
|
|||
from string import hexdigits
|
||||
from mmgen.color import *
|
||||
|
||||
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 msg(s): sys.stderr.write(s.encode('utf8') + '\n')
|
||||
def msg_r(s): sys.stderr.write(s.encode('utf8'))
|
||||
def Msg(s): sys.stdout.write(s.encode('utf8') + '\n')
|
||||
def Msg_r(s): sys.stdout.write(s.encode('utf8'))
|
||||
def msgred(s): msg(red(s))
|
||||
|
||||
def mmsg(*args):
|
||||
for d in args:
|
||||
sys.stdout.write(repr(d)+'\n')
|
||||
for d in args: Msg(repr(d))
|
||||
def mdie(*args):
|
||||
for d in args:
|
||||
sys.stdout.write(repr(d)+'\n')
|
||||
sys.exit()
|
||||
mmsg(*args); sys.exit()
|
||||
|
||||
def die_wait(delay,ev=0,s=''):
|
||||
assert type(delay) == int
|
||||
assert type(ev) == int
|
||||
if s: sys.stderr.write(s+'\n')
|
||||
if s: msg(s)
|
||||
time.sleep(delay)
|
||||
sys.exit(ev)
|
||||
def die_pause(ev=0,s=''):
|
||||
assert type(ev) == int
|
||||
if s: sys.stderr.write(s+'\n')
|
||||
if s: msg(s)
|
||||
raw_input('Press ENTER to exit')
|
||||
sys.exit(ev)
|
||||
def die(ev=0,s=''):
|
||||
assert type(ev) == int
|
||||
if s: sys.stderr.write(s+'\n')
|
||||
if s: msg(s)
|
||||
sys.exit(ev)
|
||||
def Die(ev=0,s=''):
|
||||
assert type(ev) == int
|
||||
if s: sys.stdout.write(s+'\n')
|
||||
if s: Msg(s)
|
||||
sys.exit(ev)
|
||||
|
||||
def pp_format(d):
|
||||
import pprint
|
||||
return pprint.PrettyPrinter(indent=4).pformat(d)
|
||||
|
||||
def pp_die(d):
|
||||
import pprint
|
||||
die(1,pprint.PrettyPrinter(indent=4).pformat(d))
|
||||
|
||||
def pp_msg(d):
|
||||
import pprint
|
||||
msg(pprint.PrettyPrinter(indent=4).pformat(d))
|
||||
def pp_die(d): die(1,pp_format(d))
|
||||
def pp_msg(d): msg(pp_format(d))
|
||||
|
||||
def set_for_type(val,refval,desc,invert_bool=False,src=None):
|
||||
src_str = (''," in '{}'".format(src))[bool(src)]
|
||||
|
|
@ -120,24 +112,24 @@ def check_or_create_dir(path):
|
|||
|
||||
from mmgen.opts import opt
|
||||
|
||||
def qmsg(s,alt=False):
|
||||
def qmsg(s,alt=None):
|
||||
if opt.quiet:
|
||||
if alt != False: sys.stderr.write(alt + '\n')
|
||||
else: sys.stderr.write(s + '\n')
|
||||
def qmsg_r(s,alt=False):
|
||||
if alt != None: msg(alt)
|
||||
else: msg(s)
|
||||
def qmsg_r(s,alt=None):
|
||||
if opt.quiet:
|
||||
if alt != False: sys.stderr.write(alt)
|
||||
else: sys.stderr.write(s)
|
||||
if alt != None: msg_r(alt)
|
||||
else: msg_r(s)
|
||||
def vmsg(s,force=False):
|
||||
if opt.verbose or force: sys.stderr.write(s + '\n')
|
||||
if opt.verbose or force: msg(s)
|
||||
def vmsg_r(s,force=False):
|
||||
if opt.verbose or force: sys.stderr.write(s)
|
||||
if opt.verbose or force: msg_r(s)
|
||||
def Vmsg(s,force=False):
|
||||
if opt.verbose or force: sys.stdout.write(s + '\n')
|
||||
if opt.verbose or force: Msg(s)
|
||||
def Vmsg_r(s,force=False):
|
||||
if opt.verbose or force: sys.stdout.write(s)
|
||||
if opt.verbose or force: Msg_r(s)
|
||||
def dmsg(s):
|
||||
if opt.debug: sys.stdout.write(s + '\n')
|
||||
if opt.debug: msg(s)
|
||||
|
||||
def suf(arg,suf_type):
|
||||
t = type(arg)
|
||||
|
|
@ -170,7 +162,7 @@ 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 is_chksum_6(s): return len(s) == 6 and is_hex_str_lc(s)
|
||||
|
||||
def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
|
||||
|
||||
|
|
@ -212,35 +204,55 @@ def secs_to_hms(secs):
|
|||
def secs_to_ms(secs):
|
||||
return '{:02d}:{:02d}'.format(secs/60, secs % 60)
|
||||
|
||||
def _is_whatstring(s,chars):
|
||||
return set(list(s)) <= set(chars)
|
||||
|
||||
def is_int(s):
|
||||
try:
|
||||
int(s)
|
||||
int(str(s))
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
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):
|
||||
# https://en.wikipedia.org/wiki/Base32#RFC_4648_Base32_alphabet
|
||||
# https://tools.ietf.org/html/rfc4648
|
||||
b32a = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'
|
||||
def is_b32_str(s): return set(list(s)) <= set(list(b32a))
|
||||
def is_hex_str(s): return set(list(s.lower())) <= set(list(hexdigits.lower()))
|
||||
def is_hex_str_lc(s): return set(list(s)) <= set(list(hexdigits.lower()))
|
||||
def is_hex_str_uc(s): return set(list(s)) <= set(list(hexdigits.upper()))
|
||||
def is_b58_str(s):
|
||||
from mmgen.bitcoin import b58a
|
||||
return _is_whatstring(s,b58a)
|
||||
return set(list(s)) <= set(b58a)
|
||||
|
||||
def is_utf8(s):
|
||||
try: s.decode('utf8')
|
||||
def is_ascii(s,enc='ascii'):
|
||||
try: s.decode(enc)
|
||||
except: return False
|
||||
else: return True
|
||||
else: return True
|
||||
|
||||
def is_ascii(s):
|
||||
try: s.decode('ascii')
|
||||
except: return False
|
||||
else: return True
|
||||
def is_utf8(s): return is_ascii(s,enc='utf8')
|
||||
|
||||
class baseconv(object):
|
||||
|
||||
@staticmethod
|
||||
def tohex(base,words,wl,pad=None): # accepts both string and list input
|
||||
if type(words) not in (list,tuple):
|
||||
words = tuple(words.strip())
|
||||
if not set(words).issubset(set(wl)):
|
||||
die(2,'{} is not in base-{} format'.format(repr(words_arg),base))
|
||||
deconv = [wl.index(words[::-1][i])*(base**i)
|
||||
for i in range(len(words))]
|
||||
ret = ('{:0{w}x}'.format(sum(deconv),w=pad or 0))
|
||||
return ('','0')[len(ret) % 2] + ret
|
||||
|
||||
@staticmethod
|
||||
def fromhex(base,hexnum,wl,pad=None):
|
||||
assert len(wl) == base
|
||||
hexnum = hexnum.strip()
|
||||
if not is_hex_str(hexnum):
|
||||
die(2,"'%s': not a hexadecimal number" % hexnum)
|
||||
num,ret = int(hexnum,16),[]
|
||||
while num:
|
||||
ret.append(num % base)
|
||||
num /= base
|
||||
return [wl[n] for n in [0] * ((pad or 0)-len(ret)) + ret[::-1]]
|
||||
|
||||
def match_ext(addr,ext):
|
||||
return addr.split('.')[-1] == ext
|
||||
|
|
@ -444,6 +456,9 @@ def write_data_to_file(
|
|||
if ask_write_default_yes == False or ask_write_prompt:
|
||||
ask_write = True
|
||||
|
||||
if not binary and type(data) == unicode:
|
||||
data = data.encode('utf8')
|
||||
|
||||
def do_stdout():
|
||||
qmsg('Output to STDOUT requested')
|
||||
if sys.stdout.isatty():
|
||||
|
|
@ -537,7 +552,7 @@ def get_words(infile,desc,prompt):
|
|||
def mmgen_decrypt_file_maybe(fn,desc=''):
|
||||
d = get_data_from_file(fn,desc,binary=True)
|
||||
have_enc_ext = get_extension(fn) == g.mmenc_ext
|
||||
if have_enc_ext or not is_ascii(d):
|
||||
if have_enc_ext or not is_utf8(d):
|
||||
m = ('Attempting to decrypt','Decrypting')[have_enc_ext]
|
||||
msg("%s %s '%s'" % (m,desc,fn))
|
||||
from mmgen.crypto import mmgen_decrypt_retry
|
||||
|
|
@ -547,7 +562,9 @@ def mmgen_decrypt_file_maybe(fn,desc=''):
|
|||
def get_lines_from_file(fn,desc='',trim_comments=False):
|
||||
dec = mmgen_decrypt_file_maybe(fn,desc)
|
||||
ret = dec.decode('utf8').splitlines() # DOS-safe
|
||||
return remove_comments(ret) if trim_comments else ret
|
||||
if trim_comments: ret = remove_comments(ret)
|
||||
vmsg(u"Got {} lines from file '{}'".format(len(ret),fn))
|
||||
return ret
|
||||
|
||||
def get_data_from_user(desc='data',silent=False):
|
||||
data = my_raw_input('Enter %s: ' % desc, echo=opt.echo_passphrase)
|
||||
|
|
@ -593,7 +610,7 @@ def my_raw_input(prompt,echo=True,insert_txt='',use_readline=True):
|
|||
from mmgen.term import kb_hold_protect
|
||||
kb_hold_protect()
|
||||
if echo or not sys.stdin.isatty():
|
||||
reply = raw_input(prompt)
|
||||
reply = raw_input(prompt.encode('utf8'))
|
||||
else:
|
||||
from getpass import getpass
|
||||
reply = getpass(prompt)
|
||||
|
|
|
|||
48
setup.py
48
setup.py
|
|
@ -16,16 +16,48 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys,os,subprocess
|
||||
from shutil import copy2
|
||||
_gvi = subprocess.check_output(['gcc','--version']).splitlines()[0]
|
||||
have_mingw64 = 'x86_64' in _gvi and 'MinGW' in _gvi
|
||||
have_arm = subprocess.check_output(['uname','-m']).strip() == 'aarch64'
|
||||
|
||||
# Zipfile module under Windows (MinGW) can't handle UTF-8 filenames.
|
||||
# Move it so that distutils will use the 'zip' utility instead.
|
||||
def divert_zipfile_module():
|
||||
msg1 = 'Unable to divert zipfile module. UTF-8 filenames may be broken in the Python archive.'
|
||||
def return_warn(m):
|
||||
sys.stderr.write('WARNING: {}\n'.format(m))
|
||||
return False
|
||||
|
||||
dirname = os.path.dirname(sys.modules['os'].__file__)
|
||||
if not dirname: return return_warn(msg1)
|
||||
stem = os.path.join(dirname,'zipfile')
|
||||
a,b = stem+'.py',stem+'-is-broken.py'
|
||||
|
||||
try: os.stat(a)
|
||||
except: return
|
||||
|
||||
try:
|
||||
sys.stderr.write('moving {} -> {}\n'.format(a,b))
|
||||
os.rename(a,b)
|
||||
except:
|
||||
return return_warn(msg1)
|
||||
else:
|
||||
try:
|
||||
os.unlink(stem+'.pyc')
|
||||
os.unlink(stem+'.pyo')
|
||||
except:
|
||||
pass
|
||||
|
||||
if have_mingw64:
|
||||
# import zipfile
|
||||
# sys.exit()
|
||||
divert_zipfile_module()
|
||||
|
||||
from distutils.core import setup,Extension
|
||||
from distutils.command.build_ext import build_ext
|
||||
from distutils.command.install_data import install_data
|
||||
import sys,os
|
||||
from shutil import copy2
|
||||
|
||||
import subprocess as sp
|
||||
_gvi = sp.check_output(['gcc','--version']).splitlines()[0]
|
||||
have_mingw64 = 'x86_64' in _gvi and 'MinGW' in _gvi
|
||||
have_arm = sp.check_output(['uname','-m']).strip() == 'aarch64'
|
||||
|
||||
# install extension module in repository after building
|
||||
class my_build_ext(build_ext):
|
||||
|
|
@ -100,6 +132,7 @@ setup(
|
|||
'mmgen.main',
|
||||
'mmgen.main_wallet',
|
||||
'mmgen.main_addrgen',
|
||||
'mmgen.main_passgen',
|
||||
'mmgen.main_addrimport',
|
||||
'mmgen.main_txcreate',
|
||||
'mmgen.main_txbump',
|
||||
|
|
@ -116,6 +149,7 @@ setup(
|
|||
scripts=[
|
||||
'mmgen-addrgen',
|
||||
'mmgen-keygen',
|
||||
'mmgen-passgen',
|
||||
'mmgen-addrimport',
|
||||
'mmgen-passchg',
|
||||
'mmgen-walletchk',
|
||||
|
|
|
|||
|
|
@ -121,8 +121,12 @@ if a and b:
|
|||
gen_a = get_privhex2addr_f(generator=a)
|
||||
gen_b = get_privhex2addr_f(generator=b)
|
||||
compressed = False
|
||||
for i in range(1,rounds+1):
|
||||
qmsg_r('\rRound %s/%s ' % (i,rounds))
|
||||
last_t = time.time()
|
||||
|
||||
for i in range(rounds):
|
||||
if time.time() - last_t >= 0.1:
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
last_t = time.time()
|
||||
sec = hexlify(os.urandom(32))
|
||||
wif = hex2wif(sec,compressed=compressed)
|
||||
a_addr = gen_a(sec,compressed)
|
||||
|
|
@ -132,6 +136,7 @@ if a and b:
|
|||
match_error(sec,wif,a_addr,b_addr,a,b)
|
||||
if a != 2 and b != 2:
|
||||
compressed = not compressed
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
|
||||
qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
|
||||
elif a and not fh:
|
||||
|
|
@ -139,24 +144,27 @@ elif a and not fh:
|
|||
qmsg(green(m.format(g.key_generators[a-1])))
|
||||
from mmgen.addr import get_privhex2addr_f
|
||||
gen_a = get_privhex2addr_f(generator=a)
|
||||
import time
|
||||
start = time.time()
|
||||
from struct import pack,unpack
|
||||
seed = os.urandom(28)
|
||||
print 'Incrementing key with each round'
|
||||
print 'Starting key:', hexlify(seed+pack('I',0))
|
||||
compressed = False
|
||||
import time
|
||||
start = last_t = time.time()
|
||||
|
||||
for i in range(rounds):
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
if time.time() - last_t >= 0.1:
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
last_t = time.time()
|
||||
sec = hexlify(seed+pack('I',i))
|
||||
wif = hex2wif(sec,compressed=compressed)
|
||||
a_addr = gen_a(sec,compressed)
|
||||
vmsg('\nkey: %s\naddr: %s\n' % (wif,a_addr))
|
||||
if a != 2:
|
||||
compressed = not compressed
|
||||
elapsed = int(time.time() - start)
|
||||
qmsg('')
|
||||
qmsg('%s addresses generated in %s second%s' % (rounds,elapsed,('s','')[elapsed==1]))
|
||||
qmsg_r('\rRound %s/%s ' % (i+1,rounds))
|
||||
|
||||
qmsg('\n{} addresses generated in {:.2f} seconds'.format(rounds,time.time()-start))
|
||||
elif a and dump:
|
||||
m = "Comparing output of address generator '{}' against wallet dump '{}'"
|
||||
qmsg(green(m.format(g.key_generators[a-1],cmd_args[1])))
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
# MMGen password file
|
||||
#
|
||||
# This file is editable.
|
||||
# Everything following a hash symbol '#' is a comment and ignored by MMGen.
|
||||
# A text label of 32 characters or less may be added to the right of each
|
||||
# password. The label may contain any printable ASCII symbol.
|
||||
#
|
||||
# Password data checksum for 98831F3A-фубар@crypto.org-base58-20[1,4,9-11,1100]: 7723 735B 2CBB 2571
|
||||
# Record this value to a secure location.
|
||||
98831F3A фубар@crypto.org base58:20 {
|
||||
1 7ds9PiQt1poHpknpQyNg
|
||||
4 Dp4s9nWuzCFsdy39p6tk
|
||||
9 3pPEHJdeF4vid8D7vea4
|
||||
10 iX5q85oD9hnNfg219ztp
|
||||
11 vqgKETaoP8yxVUuHYgkf
|
||||
1100 89i9jmt7s6Nh5PNLdRJH
|
||||
}
|
||||
45
test/test.py
45
test/test.py
|
|
@ -280,6 +280,8 @@ cfgs = {
|
|||
'ref_bw_seed_id': '33F10310',
|
||||
'addrfile_chk': ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE')[g.testnet],
|
||||
'keyaddrfile_chk': ('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35')[g.testnet],
|
||||
'passfile_chk': '3EA0 A3C9 DA28 5126',
|
||||
'passfile32_chk': 'EF67 D0BE 4B24 9B4F',
|
||||
'wpasswd': 'reference password',
|
||||
'ref_wallet': 'FE3C6545-D782B529[128,1].mmdat',
|
||||
'ic_wallet': 'FE3C6545-E29303EA-5E229E30[128,1].mmincog',
|
||||
|
|
@ -291,6 +293,7 @@ cfgs = {
|
|||
'tmpdir': os.path.join('test','tmp6'),
|
||||
'kapasswd': '',
|
||||
'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses
|
||||
'pass_idx_list': '1,4,9-11,1100',
|
||||
'dep_generators': {
|
||||
'mmdat': 'refwalletgen1',
|
||||
pwfile: 'refwalletgen1',
|
||||
|
|
@ -306,6 +309,8 @@ cfgs = {
|
|||
'ref_bw_seed_id': 'CE918388',
|
||||
'addrfile_chk': ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81')[g.testnet],
|
||||
'keyaddrfile_chk': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC')[g.testnet],
|
||||
'passfile_chk': '000C 7711 CD45 C5BE',
|
||||
'passfile32_chk': 'AFEC 54A1 7D79 1866',
|
||||
'wpasswd': 'reference password',
|
||||
'ref_wallet': '1378FC64-6F0F9BB4[192,1].mmdat',
|
||||
'ic_wallet': '1378FC64-2907DE97-F980D21F[192,1].mmincog',
|
||||
|
|
@ -317,6 +322,7 @@ cfgs = {
|
|||
'tmpdir': os.path.join('test','tmp7'),
|
||||
'kapasswd': '',
|
||||
'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses
|
||||
'pass_idx_list': '1,4,9-11,1100',
|
||||
'dep_generators': {
|
||||
'mmdat': 'refwalletgen2',
|
||||
pwfile: 'refwalletgen2',
|
||||
|
|
@ -332,13 +338,16 @@ cfgs = {
|
|||
'ref_bw_seed_id': 'B48CD7FC',
|
||||
'addrfile_chk': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
|
||||
'keyaddrfile_chk': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
|
||||
'passfile_chk': '54B1 A5BE 9F07 1FDD',
|
||||
'passfile32_chk': '072A 4A13 FB64 B64B',
|
||||
'wpasswd': 'reference password',
|
||||
'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
|
||||
'ref_addrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
|
||||
'ref_keyaddrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(tn_desc),
|
||||
'ref_passwdfile': '98831F3A-фубар@crypto.org-base58-20[1,4,9-11,1100].pws',
|
||||
'ref_addrfile_chksum': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
|
||||
'ref_keyaddrfile_chksum': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
|
||||
|
||||
'ref_passwdfile_chksum': '7723 735B 2CBB 2571',
|
||||
# 'ref_fake_unspent_data':'98831F3A_unspent.json',
|
||||
'ref_tx_file': 'FFB367[1.234]{}.rawtx'.format(tn_desc),
|
||||
'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog',
|
||||
|
|
@ -350,6 +359,8 @@ cfgs = {
|
|||
'tmpdir': os.path.join('test','tmp8'),
|
||||
'kapasswd': '',
|
||||
'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses
|
||||
'pass_idx_list': '1,4,9-11,1100',
|
||||
|
||||
'dep_generators': {
|
||||
'mmdat': 'refwalletgen3',
|
||||
pwfile: 'refwalletgen3',
|
||||
|
|
@ -470,13 +481,16 @@ cmd_group['ref'] = (
|
|||
# generating new reference ('abc' brainwallet) files:
|
||||
('refwalletgen', ([],'gen new refwallet')),
|
||||
('refaddrgen', (['mmdat',pwfile],'new refwallet addr chksum')),
|
||||
('refkeyaddrgen', (['mmdat',pwfile],'new refwallet key-addr chksum'))
|
||||
('refkeyaddrgen', (['mmdat',pwfile],'new refwallet key-addr chksum')),
|
||||
('refpasswdgen', (['mmdat',pwfile],'new refwallet passwd file chksum')),
|
||||
('ref_b32passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
|
||||
)
|
||||
|
||||
# misc. saved reference data
|
||||
cmd_group['ref_other'] = (
|
||||
('ref_addrfile_chk', 'saved reference address file'),
|
||||
('ref_keyaddrfile_chk','saved reference key-address file'),
|
||||
('ref_passwdfile_chk', 'saved reference password file'),
|
||||
# Create the fake inputs:
|
||||
# ('txcreate8', 'transaction creation (8)'),
|
||||
('ref_tx_chk', 'saved reference tx file'),
|
||||
|
|
@ -1351,17 +1365,20 @@ class MMGenTestSuite(object):
|
|||
have_dfl_wallet = False
|
||||
if not ia: ok()
|
||||
|
||||
def addrgen(self,name,wf,pf=None,check_ref=False):
|
||||
add_args = ([],['-q'] + ([],['-P',pf])[bool(pf)])[ia]
|
||||
t = MMGenExpect(name,'mmgen-addrgen', add_args +
|
||||
['-d',cfg['tmpdir']] + ([],[wf])[bool(wf)] + [cfg['addr_idx_list']])
|
||||
def addrgen(self,name,wf,pf=None,check_ref=False,ftype='addr',id_str=None,extra_args=[]):
|
||||
ftype,chkfile = ((ftype,'{}file_chk'.format(ftype)),('pass','passfile32_chk'))[ftype=='pass32']
|
||||
add_args = extra_args + ([],['-q'] + ([],['-P',pf])[bool(pf)])[ia]
|
||||
dlist = [id_str] if id_str else []
|
||||
t = MMGenExpect(name,'mmgen-{}gen'.format(ftype), add_args +
|
||||
['-d',cfg['tmpdir']] + ([],[wf])[bool(wf)] + dlist + [cfg['{}_idx_list'.format(ftype)]])
|
||||
if ia: return
|
||||
t.license()
|
||||
t.passphrase('MMGen wallet',cfg['wpasswd'])
|
||||
t.expect('Passphrase is OK')
|
||||
chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True)
|
||||
desc = ('address','password')[ftype=='pass']
|
||||
chk = t.expect_getend(r'Checksum for {} data .*?: '.format(desc),regex=True)
|
||||
if check_ref:
|
||||
refcheck('address data checksum',chk,cfg['addrfile_chk'])
|
||||
refcheck('address data checksum',chk,cfg[chkfile])
|
||||
return
|
||||
t.written_to_file('Addresses',oo=True)
|
||||
t.ok()
|
||||
|
|
@ -1702,6 +1719,13 @@ class MMGenTestSuite(object):
|
|||
def refkeyaddrgen(self,name,wf,pf):
|
||||
self.keyaddrgen(name,wf,pf,check_ref=True)
|
||||
|
||||
def refpasswdgen(self,name,wf,pf):
|
||||
self.addrgen(name,wf,pf,check_ref=True,ftype='pass',id_str='alice@crypto.org')
|
||||
|
||||
def ref_b32passwdgen(self,name,wf,pf):
|
||||
ea = ['--base32','--passwd-len','17']
|
||||
self.addrgen(name,wf,pf,check_ref=True,ftype='pass32',id_str='фубар@crypto.org',extra_args=ea)
|
||||
|
||||
def txsign_keyaddr(self,name,keyaddr_file,txfile):
|
||||
t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],'-M',keyaddr_file,txfile])
|
||||
t.license()
|
||||
|
|
@ -2016,6 +2040,9 @@ class MMGenTestSuite(object):
|
|||
def ref_keyaddrfile_chk(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='keyaddr')
|
||||
|
||||
def ref_passwdfile_chk(self,name):
|
||||
self.ref_addrfile_chk(name,ftype='passwd')
|
||||
|
||||
# def txcreate8(self,name,addrfile):
|
||||
# self.txcreate_common(name,sources=['8'])
|
||||
|
||||
|
|
@ -2171,6 +2198,8 @@ class MMGenTestSuite(object):
|
|||
'ref_brain_chk',
|
||||
'ref_hincog_chk',
|
||||
'refkeyaddrgen',
|
||||
'refpasswdgen',
|
||||
'ref_b32passwdgen'
|
||||
):
|
||||
for i in ('1','2','3'):
|
||||
locals()[k+i] = locals()[k]
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ if opt.list_cmds:
|
|||
|
||||
import binascii
|
||||
from mmgen.test import *
|
||||
from mmgen.tx import is_wif,is_btc_addr,is_b58_str
|
||||
from mmgen.tx import is_wif,is_btc_addr
|
||||
|
||||
class MMGenToolTestSuite(object):
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue