* New incognito format with checksum for password verification. Old format
continues to be supported with '--old-incog-fmt' option * mmgen-txsign: '--mmgen-keys-from-file' option (supersedes '--all-keys-from-file' option) allows offline signing of transactions with both MMGen and non-MMGen inputs. Instead of a flat keylist, a key-address file (the output of 'mmgen-keygen'), optionally encrypted, is used both as a key source and to verify MMGen-to-BTC mappings for both inputs and outputs, eliminating the need for an additional address file. * mmgen-addrimport: '--keyaddr-file' option allows using key-address file (possibly encrypted) as an address source.
This commit is contained in:
parent
964451f701
commit
4378ac0db9
19 changed files with 1255 additions and 1205 deletions
|
|
@ -57,7 +57,7 @@ def parse_opts(argv,help_data):
|
|||
('outdir', 'export_incog_hidden'),
|
||||
('from_incog_hidden','from_incog','from_seed','from_mnemonic','from_brain'),
|
||||
('export_incog','export_incog_hex','export_incog_hidden','export_mnemonic',
|
||||
'export_seed'),
|
||||
'export_seed'),
|
||||
('quiet','verbose')
|
||||
): warn_incompatible_opts(opts,l)
|
||||
|
||||
|
|
@ -65,12 +65,14 @@ def parse_opts(argv,help_data):
|
|||
if not check_opts(opts,long_opts): sys.exit(1)
|
||||
|
||||
# If unset, set these to default values in mmgen.config:
|
||||
for v in g.cl_override_vars:
|
||||
for v in g.dfl_vars:
|
||||
if v in opts: typeconvert_override_var(opts,v)
|
||||
else: opts[v] = eval("g."+v)
|
||||
|
||||
if "verbose" in opts: g.verbose = True
|
||||
if "quiet" in opts: g.quiet = True
|
||||
# Opposite of above: if set, override the default values in mmgen.config:
|
||||
if 'no_keyconv' in opts: g.no_keyconv = opts['no_keyconv']
|
||||
if 'verbose' in opts: g.verbose = opts['verbose']
|
||||
if 'quiet' in opts: g.quiet = opts['quiet']
|
||||
|
||||
if g.debug: print "opts after typeconvert: %s" % opts
|
||||
|
||||
|
|
@ -123,8 +125,8 @@ def check_opts(opts,long_opts):
|
|||
what = "parameter for '--%s' option" % opt.replace("_","-")
|
||||
|
||||
# Check for file existence and readability
|
||||
if opt in ('keys_from_file','all_keys_from_file','addrlist',
|
||||
'passwd_file','keysforaddrs'):
|
||||
if opt in ('keys_from_file','mmgen_keys_from_file',
|
||||
'passwd_file','keysforaddrs','comment_file'):
|
||||
check_infile(val) # exits on error
|
||||
continue
|
||||
|
||||
|
|
@ -170,7 +172,7 @@ def check_opts(opts,long_opts):
|
|||
if not opt_is_in_list(val,g.hash_presets.keys(),what): return False
|
||||
elif opt == 'usr_randchars':
|
||||
if not opt_is_int(val,what): return False
|
||||
if val == '0': return True
|
||||
if val == '0': continue
|
||||
if not opt_compares(val,">=",g.min_urandchars,what): return False
|
||||
if not opt_compares(val,"<=",g.max_urandchars,what): return False
|
||||
else:
|
||||
|
|
@ -187,6 +189,7 @@ def typeconvert_override_var(opts,opt):
|
|||
if vtype == int: f,t = int,"an integer"
|
||||
elif vtype == str: f,t = str,"a string"
|
||||
elif vtype == float: f,t = float,"a float"
|
||||
elif vtype == bool: f,t = bool,"a boolean value"
|
||||
|
||||
try:
|
||||
opts[opt] = f(opts[opt])
|
||||
|
|
|
|||
|
|
@ -58,10 +58,10 @@ def test_for_keyconv():
|
|||
return True
|
||||
|
||||
|
||||
def generate_addrs(seed, addrnums, opts):
|
||||
def generate_addrs(seed, addrnums, opts, seed_id=""):
|
||||
|
||||
if 'addrs' in opts['gen_what']:
|
||||
if 'no_keyconv' in opts or test_for_keyconv() == False:
|
||||
if 'a' in opts['gen_what']:
|
||||
if g.no_keyconv or test_for_keyconv() == False:
|
||||
msg("Using (slow) internal ECDSA library for address generation")
|
||||
from mmgen.bitcoin import privnum2addr
|
||||
keyconv = False
|
||||
|
|
@ -69,18 +69,21 @@ def generate_addrs(seed, addrnums, opts):
|
|||
from subprocess import Popen, PIPE
|
||||
keyconv = "keyconv"
|
||||
|
||||
fmt = "num addr" if opts['gen_what'] == ["addrs"] else (
|
||||
"num sec wif" if opts['gen_what'] == ["keys"] else "num sec wif addr")
|
||||
fmt = "num sec wif addr" if 'ka' in opts['gen_what'] else (
|
||||
"num sec wif" if 'k' in opts['gen_what'] else "num addr")
|
||||
|
||||
from collections import namedtuple
|
||||
addrinfo = namedtuple("addrinfo",fmt)
|
||||
addrinfo_args = "%s" % ",".join(fmt.split())
|
||||
|
||||
addrnums = sorted(set(addrnums)) # don't trust the calling function
|
||||
t_addrs,num,pos,out = len(addrnums),0,0,[]
|
||||
addrnums.sort() # needed only if caller didn't sort
|
||||
|
||||
ws,wp = ('key','keys') if 'keys' in opts['gen_what'] \
|
||||
else ('address','addresses')
|
||||
w = {
|
||||
'ka': ('key/address pair','s'),
|
||||
'k': ('key','s'),
|
||||
'a': ('address','es')
|
||||
}[opts['gen_what']]
|
||||
|
||||
while pos != t_addrs:
|
||||
seed = sha512(seed).digest()
|
||||
|
|
@ -91,50 +94,51 @@ def generate_addrs(seed, addrnums, opts):
|
|||
|
||||
pos += 1
|
||||
|
||||
qmsg_r("\rGenerating %s #%s (%s of %s)" % (ws,num,pos,t_addrs))
|
||||
qmsg_r("\rGenerating %s #%s (%s of %s)" % (w[0],num,pos,t_addrs))
|
||||
|
||||
# Secret key is double sha256 of seed hash round /num/
|
||||
sec = sha256(sha256(seed).digest()).hexdigest()
|
||||
wif = numtowif(int(sec,16))
|
||||
|
||||
if 'addrs' in opts['gen_what']: addr = \
|
||||
if 'a' in opts['gen_what']: addr = \
|
||||
Popen([keyconv, wif], stdout=PIPE).stdout.readline().split()[1] \
|
||||
if keyconv else privnum2addr(int(sec,16))
|
||||
|
||||
out.append(eval("addrinfo("+addrinfo_args+")"))
|
||||
|
||||
qmsg("\rGenerated %s %s%s"%(t_addrs, (ws if t_addrs == 1 else wp), " "*15))
|
||||
m = w[0] if t_addrs == 1 else w[0]+w[1]
|
||||
if seed_id:
|
||||
qmsg("\r%s: %s %s generated%s" % (seed_id,t_addrs,m," "*15))
|
||||
else:
|
||||
qmsg("\rGenerated %s %s%s" % (t_addrs,m," "*15))
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def format_addr_data(addr_data, addr_data_chksum, seed_id, addr_idxs, opts):
|
||||
|
||||
if 'flat_list' in opts:
|
||||
return "\n\n".join(["# {}:{d.num} {d.addr}\n{d.wif}".format(seed_id,d=d)
|
||||
for d in addr_data])+"\n\n"
|
||||
|
||||
fs = " {:<%s} {}" % len(str(addr_data[-1].num))
|
||||
|
||||
if 'addrs' not in opts['gen_what']: out = []
|
||||
else:
|
||||
if 'a' in opts['gen_what']:
|
||||
out = [] if 'stdout' in opts else [addrmsgs['addrfile_header']+"\n"]
|
||||
out.append("# Address data checksum for {}[{}]: {}".format(
|
||||
seed_id, fmt_addr_idxs(addr_idxs), addr_data_chksum))
|
||||
w = "Key-address" if 'k' in opts['gen_what'] else "Address"
|
||||
out.append("# {} data checksum for {}[{}]: {}".format(
|
||||
w, seed_id, fmt_addr_idxs(addr_idxs), addr_data_chksum))
|
||||
out.append("# Record this value to a secure location\n")
|
||||
else: out = []
|
||||
|
||||
out.append("%s {" % seed_id.upper())
|
||||
|
||||
for d in addr_data:
|
||||
if 'addrs' in opts['gen_what']: # First line with number
|
||||
if 'a' in opts['gen_what']: # First line with number
|
||||
out.append(fs.format(d.num, d.addr))
|
||||
else:
|
||||
out.append(fs.format(d.num, "wif: "+d.wif))
|
||||
|
||||
if 'keys' in opts['gen_what']: # Subsequent lines
|
||||
if 'k' in opts['gen_what']: # Subsequent lines
|
||||
if 'b16' in opts:
|
||||
out.append(fs.format("", "hex: "+d.sec))
|
||||
if 'addrs' in opts['gen_what']:
|
||||
if 'a' in opts['gen_what']:
|
||||
out.append(fs.format("", "wif: "+d.wif))
|
||||
|
||||
out.append("}")
|
||||
|
|
|
|||
|
|
@ -52,65 +52,56 @@ b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'
|
|||
# The "zero address":
|
||||
# 1111111111111111111114oLvT2 (use step2 = ("0" * 40) to generate)
|
||||
#
|
||||
|
||||
def pubhex2hexaddr(pubhex):
|
||||
step1 = sha256(unhexlify(pubhex)).digest()
|
||||
return hashlib_new('ripemd160',step1).hexdigest()
|
||||
|
||||
def hexaddr2addr(hexaddr):
|
||||
def hexaddr2addr(hexaddr, vers_num='00'):
|
||||
# See above:
|
||||
extra_ones = (len(hexaddr) - len(hexaddr.lstrip("0"))) / 2
|
||||
step1 = sha256(unhexlify('00'+hexaddr)).digest()
|
||||
hexaddr2 = vers_num + hexaddr
|
||||
step1 = sha256(unhexlify(hexaddr2)).digest()
|
||||
step2 = sha256(step1).hexdigest()
|
||||
pubkey = int(hexaddr + step2[:8], 16)
|
||||
return "1" + ("1" * extra_ones) + _numtob58(pubkey)
|
||||
|
||||
def pubhex2addr(pubhex):
|
||||
return hexaddr2addr(pubhex2hexaddr(pubhex))
|
||||
pubkey = hexaddr2 + step2[:8]
|
||||
lzeroes = (len(hexaddr2) - len(hexaddr2.lstrip("0"))) / 2
|
||||
return ("1" * lzeroes) + _numtob58(int(pubkey,16))
|
||||
|
||||
def verify_addr(addr,verbose=False,return_hex=False):
|
||||
|
||||
if addr[0] != "1":
|
||||
if verbose: print "%s: Invalid address" % addr
|
||||
return False
|
||||
for vers_num,ldigit in ('00','1'),('05','3'):
|
||||
if addr[0] != ldigit: continue
|
||||
num = _b58tonum(addr)
|
||||
if num == False: break
|
||||
addr_hex = "{:050x}".format(num)
|
||||
if addr_hex[:2] != vers_num: continue
|
||||
step1 = sha256(unhexlify(addr_hex[:42])).digest()
|
||||
step2 = sha256(step1).hexdigest()
|
||||
if step2[:8] == addr_hex[42:]:
|
||||
return addr_hex[2:42] if return_hex else True
|
||||
else:
|
||||
if verbose: print "Invalid checksum in address '%s'" % addr
|
||||
break
|
||||
|
||||
num = _b58tonum(addr[1:])
|
||||
if num == False: return False
|
||||
addr_hex = hex(num)[2:].rstrip("L").zfill(48)
|
||||
if verbose: print "Invalid address '%s'" % addr
|
||||
return False
|
||||
|
||||
step1 = sha256(unhexlify('00'+addr_hex[:40])).digest()
|
||||
step2 = sha256(step1).hexdigest()
|
||||
|
||||
if step2[:8] != addr_hex[40:]:
|
||||
if verbose: print "Invalid checksum in address %s" % ("1" + addr)
|
||||
return False
|
||||
|
||||
return addr_hex[:40] if return_hex else True
|
||||
|
||||
# Reworked code from here:
|
||||
|
||||
def _numtob58(num):
|
||||
b58conv,i = [],0
|
||||
while True:
|
||||
n = num / (58**i); i += 1
|
||||
if not n: break
|
||||
b58conv.append(b58a[n % 58])
|
||||
return ''.join(b58conv)[::-1]
|
||||
ret = []
|
||||
while num:
|
||||
ret.append(b58a[num % 58])
|
||||
num /= 58
|
||||
return ''.join(ret)[::-1]
|
||||
|
||||
def _b58tonum(b58num):
|
||||
for i in b58num:
|
||||
if not i in b58a:
|
||||
print "Invalid symbol in b58 number: '%s'" % i
|
||||
return False
|
||||
|
||||
b58deconv = []
|
||||
b58num_r = b58num[::-1]
|
||||
for i in range(len(b58num)):
|
||||
idx = b58a.index(b58num_r[i])
|
||||
b58deconv.append(idx * (58**i))
|
||||
return sum(b58deconv)
|
||||
if not i in b58a: return False
|
||||
return sum([b58a.index(n) * (58**i) for i,n in enumerate(list(b58num[::-1]))])
|
||||
|
||||
def numtowif(numpriv):
|
||||
step1 = '80'+hex(numpriv)[2:].rstrip('L').zfill(64)
|
||||
step1 = '80' + "{:064x}".format(numpriv)
|
||||
step2 = sha256(unhexlify(step1)).digest()
|
||||
step3 = sha256(step2).hexdigest()
|
||||
key = step1 + step3[:8]
|
||||
|
|
@ -131,8 +122,8 @@ def b58decode(b58num):
|
|||
# Zap all spaces:
|
||||
num = _b58tonum(b58num.translate(None,' \t\n\r'))
|
||||
if num == False: return False
|
||||
out = hex(num)[2:].rstrip('L')
|
||||
return unhexlify("0" + out if len(out) % 2 else out)
|
||||
out = "{:x}".format(num)
|
||||
return unhexlify("0"*(len(out)%2) + out)
|
||||
|
||||
# These yield bytewise equivalence in our special cases:
|
||||
|
||||
|
|
@ -165,7 +156,7 @@ def wiftohex(wifpriv,compressed=False):
|
|||
idx = 68 if compressed else 66
|
||||
num = _b58tonum(wifpriv)
|
||||
if num == False: return False
|
||||
key = hex(num)[2:].rstrip('L')
|
||||
key = "{:x}".format(num)
|
||||
if compressed and key[66:68] != '01': return False
|
||||
round1 = sha256(unhexlify(key[:idx])).digest()
|
||||
round2 = sha256(round1).hexdigest()
|
||||
|
|
@ -182,16 +173,10 @@ def privnum2pubhex(numpriv,compressed=False):
|
|||
pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1)
|
||||
pubkey = hexlify(pko.get_verifying_key().to_string())
|
||||
if compressed:
|
||||
p = '03' if pubkey[-1] in "13579bdf" else '02'
|
||||
p = '02' if pubkey[-1] in "02468ace" else '03'
|
||||
return p+pubkey[:64]
|
||||
else:
|
||||
return '04'+pubkey
|
||||
|
||||
def privnum2addr(numpriv,compressed=False):
|
||||
return pubhex2addr(privnum2pubhex(numpriv,compressed))
|
||||
|
||||
# Used only in test suite. To check validity, recode with numtowif()
|
||||
def wiftonum(wifpriv):
|
||||
num = _b58tonum(wifpriv)
|
||||
if num == False: return False
|
||||
return (num % (1<<288)) >> 32
|
||||
return hexaddr2addr(pubhex2hexaddr(privnum2pubhex(numpriv,compressed)))
|
||||
|
|
|
|||
|
|
@ -28,7 +28,8 @@ email = "<mmgen-py@yandex.com>"
|
|||
Cdates = '2013-2014'
|
||||
version = '0.7.7'
|
||||
|
||||
quiet,verbose = False,False
|
||||
quiet,verbose,no_keyconv = False,False,False
|
||||
|
||||
min_screen_width = 80
|
||||
max_tx_comment_len = 72
|
||||
|
||||
|
|
@ -52,18 +53,18 @@ sigtx_ext = "sig"
|
|||
addrfile_ext = "addrs"
|
||||
addrfile_chksum_ext = "chk"
|
||||
keyfile_ext = "keys"
|
||||
keylist_ext = "keylist"
|
||||
keyaddrfile_ext = "akeys"
|
||||
mmenc_ext = "mmenc"
|
||||
|
||||
default_wl = "electrum"
|
||||
#default_wl = "tirosh"
|
||||
|
||||
cl_override_vars = 'seed_len','hash_preset','usr_randchars'
|
||||
dfl_vars = "seed_len","hash_preset","usr_randchars"
|
||||
|
||||
seed_lens = 128,192,256
|
||||
seed_len = 256
|
||||
|
||||
mnemonic_lens = [i / 32 * 3 for i in seed_lens]
|
||||
mn_lens = [i / 32 * 3 for i in seed_lens]
|
||||
|
||||
http_timeout = 30
|
||||
|
||||
|
|
|
|||
200
mmgen/crypto.py
200
mmgen/crypto.py
|
|
@ -28,13 +28,41 @@ import mmgen.config as g
|
|||
from mmgen.util import *
|
||||
from mmgen.term import get_char
|
||||
|
||||
crmsg = {
|
||||
'incog_iv_id': """
|
||||
Check that the generated Incog ID above is correct.
|
||||
If it's not, then your incognito data is incorrect or corrupted.
|
||||
""",
|
||||
'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.
|
||||
""",
|
||||
'usr_rand_notice': """
|
||||
You've chosen to not fully trust your OS's random number generator and provide
|
||||
some additional entropy of your own. Please type %s symbols on your keyboard.
|
||||
Type slowly and choose your symbols carefully for maximum randomness. Try to
|
||||
use both upper and lowercase as well as punctuation and numerals. What you
|
||||
type will not be displayed on the screen. Note that the timings between your
|
||||
keystrokes will also be used as a source of randomness.
|
||||
""",
|
||||
'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 encrypt_seed(seed, key):
|
||||
return encrypt_data(seed, key, iv=1, what="seed")
|
||||
|
||||
|
||||
def decrypt_seed(enc_seed, key, seed_id, key_id):
|
||||
|
||||
vmsg("Checking key...")
|
||||
vmsg_r("Checking key...")
|
||||
chk1 = make_chksum_8(key)
|
||||
if key_id:
|
||||
if not compare_checksums(chk1, "of key", key_id, "in header"):
|
||||
|
|
@ -56,12 +84,14 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
|
|||
else:
|
||||
msg("Incorrect passphrase")
|
||||
|
||||
vmsg("")
|
||||
return False
|
||||
# else:
|
||||
# qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
|
||||
|
||||
if g.debug: print "Decrypted seed: %s" % hexlify(dec_seed)
|
||||
|
||||
vmsg("OK")
|
||||
return dec_seed
|
||||
|
||||
|
||||
|
|
@ -97,7 +127,7 @@ def encrypt_data(data, key, iv=1, what="data", verify=True):
|
|||
|
||||
def decrypt_data(enc_data, key, iv=1, what="data"):
|
||||
|
||||
vmsg("Decrypting %s with key..." % what)
|
||||
vmsg_r("Decrypting %s with key..." % what)
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import Counter
|
||||
|
|
@ -119,10 +149,10 @@ def scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
|
|||
return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
|
||||
|
||||
|
||||
def make_key(passwd, salt, hash_preset, what="key", verbose=False):
|
||||
def make_key(passwd, salt, hash_preset, what="encryption key", verbose=False):
|
||||
|
||||
if g.verbose or verbose:
|
||||
msg_r("Generating %s. Please wait..." % what)
|
||||
msg_r("Generating %s from passphrase.\nPlease wait..." % what)
|
||||
key = scrypt_hash_passphrase(passwd, salt, hash_preset)
|
||||
if g.verbose or verbose:
|
||||
msg("done")
|
||||
|
|
@ -133,7 +163,7 @@ def make_key(passwd, salt, hash_preset, what="key", verbose=False):
|
|||
def get_random_data_from_user(uchars):
|
||||
|
||||
if g.quiet: msg("Enter %s random symbols" % uchars)
|
||||
else: msg(cmessages['usr_rand_notice'] % uchars)
|
||||
else: msg(crmsg['usr_rand_notice'] % uchars)
|
||||
|
||||
prompt = "You may begin typing. %s symbols left: "
|
||||
msg_r(prompt % uchars)
|
||||
|
|
@ -187,26 +217,60 @@ def get_random(length,opts):
|
|||
def get_seed_from_wallet(
|
||||
infile,
|
||||
opts,
|
||||
prompt="{} wallet".format(g.proj_name),
|
||||
prompt_info="{} wallet".format(g.proj_name),
|
||||
silent=False
|
||||
):
|
||||
|
||||
wdata = get_data_from_wallet(infile,silent=silent)
|
||||
label,metadata,hash_preset,salt,enc_seed = wdata
|
||||
|
||||
if g.verbose: display_control_data(*wdata)
|
||||
if g.debug: display_control_data(*wdata)
|
||||
|
||||
passwd = get_mmgen_passphrase(prompt,opts)
|
||||
padd = " "+infile if g.quiet else ""
|
||||
passwd = get_mmgen_passphrase(prompt_info+padd,opts)
|
||||
|
||||
key = make_key(passwd, salt, hash_preset)
|
||||
|
||||
return decrypt_seed(enc_seed, key, metadata[0], metadata[1])
|
||||
|
||||
|
||||
def get_hidden_incog_data(opts):
|
||||
# Already sanity-checked:
|
||||
fname,offset,seed_len = opts['from_incog_hidden'].split(",")
|
||||
qmsg("Getting hidden incog data from file '%s'" % fname)
|
||||
|
||||
z = 0 if 'old_incog_fmt' in opts else 8
|
||||
dlen = g.aesctr_iv_len + g.salt_len + (int(seed_len)/8) + z
|
||||
|
||||
fsize = check_data_fits_file_at_offset(fname,int(offset),dlen,"read")
|
||||
|
||||
import os
|
||||
f = os.open(fname,os.O_RDONLY)
|
||||
os.lseek(f, int(offset), os.SEEK_SET)
|
||||
data = os.read(f, dlen)
|
||||
os.close(f)
|
||||
qmsg("Data read from file '%s' at offset %s" % (fname,offset),
|
||||
"Data read from file")
|
||||
return data
|
||||
|
||||
def confirm_old_format():
|
||||
|
||||
while True:
|
||||
reply = get_char(
|
||||
crmsg['incorrect_incog_passphrase_try_again']+" ").strip("\n\r")
|
||||
if not reply: msg(""); return False
|
||||
elif reply in 'yY': msg(""); return False
|
||||
elif reply in 'nN': msg("\nExiting at user request"); sys.exit(1)
|
||||
elif reply in 'mM': msg(""); return True
|
||||
else:
|
||||
if g.verbose: msg("\nInvalid reply")
|
||||
else: msg_r("\r")
|
||||
|
||||
|
||||
def get_seed_from_incog_wallet(
|
||||
infile,
|
||||
opts,
|
||||
prompt_what="{} incognito wallet".format(g.proj_name),
|
||||
prompt_info="{} incognito wallet".format(g.proj_name),
|
||||
silent=False,
|
||||
hex_input=False
|
||||
):
|
||||
|
|
@ -224,75 +288,66 @@ def get_seed_from_incog_wallet(
|
|||
msg("Data in file '%s' is not in hexadecimal format" % infile)
|
||||
sys.exit(2)
|
||||
# File could be of invalid length, so check:
|
||||
valid_dlens = [i/8 + g.aesctr_iv_len + g.salt_len for i in g.seed_lens]
|
||||
z = 0 if 'old_incog_fmt' in opts else 8
|
||||
valid_dlens = [i/8 + g.aesctr_iv_len + g.salt_len + z for i in g.seed_lens]
|
||||
# New fmt: [56, 64, 72]. Old fmt: [48, 56, 64].
|
||||
if len(d) not in valid_dlens:
|
||||
qmsg("Invalid incognito file size: %s. Valid sizes (in bytes): %s" %
|
||||
(len(d), " ".join([str(i) for i in valid_dlens]))
|
||||
)
|
||||
vn = [i/8 + g.aesctr_iv_len + g.salt_len + 8 for i in g.seed_lens]
|
||||
if len(d) in vn:
|
||||
msg("Re-run the program without the '--old-incog-fmt' option")
|
||||
sys.exit()
|
||||
else: qmsg(
|
||||
"Invalid incognito file size: %s. Valid sizes (in bytes): %s" %
|
||||
(len(d), " ".join([str(i) for i in valid_dlens])))
|
||||
return False
|
||||
|
||||
iv, enc_incog_data = d[0:g.aesctr_iv_len], d[g.aesctr_iv_len:]
|
||||
|
||||
msg("Incog ID: %s (IV ID: %s)" % (make_iv_chksum(iv),make_chksum_8(iv)))
|
||||
incog_id = make_iv_chksum(iv)
|
||||
msg("Incog ID: %s (IV ID: %s)" % (incog_id,make_chksum_8(iv)))
|
||||
qmsg("Check the applicable value against your records.")
|
||||
vmsg(cmessages['incog_iv_id_hidden' if "from_incog_hidden" in opts
|
||||
vmsg(crmsg['incog_iv_id_hidden' if "from_incog_hidden" in opts
|
||||
else 'incog_iv_id'])
|
||||
|
||||
passwd = get_mmgen_passphrase(prompt_what,opts)
|
||||
|
||||
qmsg("Configured hash presets: %s" % " ".join(sorted(g.hash_presets)))
|
||||
while True:
|
||||
p = "Enter hash preset for %s wallet (default='%s'): "
|
||||
hp = my_raw_input(p % (g.proj_name, g.hash_preset))
|
||||
if not hp:
|
||||
hp = g.hash_preset; break
|
||||
elif hp in g.hash_presets:
|
||||
break
|
||||
msg("%s: Invalid hash preset" % hp)
|
||||
passwd = get_mmgen_passphrase(prompt_info+" "+incog_id,opts)
|
||||
|
||||
# IV is used BOTH to initialize counter and to salt password!
|
||||
key = make_key(passwd, iv, hp, "wrapper key")
|
||||
d = decrypt_data(enc_incog_data, key, int(hexlify(iv),16), "incog data")
|
||||
if d == False: sys.exit(2)
|
||||
qmsg("Configured hash presets: %s" % " ".join(sorted(g.hash_presets)))
|
||||
hp = get_hash_preset_from_user(what="incog wallet")
|
||||
|
||||
salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
|
||||
# IV is used BOTH to initialize counter and to salt password!
|
||||
key = make_key(passwd, iv, hp, "wrapper key")
|
||||
d = decrypt_data(enc_incog_data, key, int(hexlify(iv),16), "incog data")
|
||||
|
||||
key = make_key(passwd, salt, hp, "main key")
|
||||
vmsg("Key ID: %s" % make_chksum_8(key))
|
||||
salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
|
||||
|
||||
seed = decrypt_seed(enc_seed, key, "", "")
|
||||
qmsg("Seed ID: %s. Check that this value is correct." % make_chksum_8(seed))
|
||||
vmsg(cmessages['incog_key_id_hidden' if "from_incog_hidden" in opts
|
||||
else 'incog_key_id'])
|
||||
key = make_key(passwd, salt, hp, "main key")
|
||||
vmsg("Key ID: %s" % make_chksum_8(key))
|
||||
|
||||
seed = decrypt_seed(enc_seed, key, "", "")
|
||||
old_fmt_sid = make_chksum_8(seed)
|
||||
|
||||
def confirm_correct_seed_id(sid):
|
||||
m = "Seed ID: %s. Is the Seed ID correct?" % sid
|
||||
return keypress_confirm(m, True)
|
||||
|
||||
if 'old_incog_fmt' in opts:
|
||||
if confirm_correct_seed_id(old_fmt_sid):
|
||||
break
|
||||
else:
|
||||
chk,seed_maybe = seed[:8],seed[8:]
|
||||
if sha256(seed_maybe).digest()[:8] == chk:
|
||||
msg("Passphrase and hash preset are correct")
|
||||
seed = seed_maybe
|
||||
break
|
||||
elif confirm_old_format():
|
||||
if confirm_correct_seed_id(old_fmt_sid):
|
||||
break
|
||||
|
||||
return seed
|
||||
|
||||
|
||||
def wallet_to_incog_data(infile,opts):
|
||||
|
||||
d = get_data_from_wallet(infile,silent=True)
|
||||
seed_id,key_id,preset,salt,enc_seed = \
|
||||
d[1][0], d[1][1], d[2].split(":")[0], d[3], d[4]
|
||||
|
||||
passwd = get_mmgen_passphrase("{} wallet".format(g.proj_name),opts)
|
||||
key = make_key(passwd, salt, preset, "main key")
|
||||
# We don't need the seed; just do this to verify password.
|
||||
if decrypt_seed(enc_seed, key, seed_id, key_id) == False:
|
||||
sys.exit(2)
|
||||
|
||||
iv = get_random(g.aesctr_iv_len,opts)
|
||||
iv_id = make_iv_chksum(iv)
|
||||
msg("Incog ID: %s" % iv_id)
|
||||
|
||||
# IV is used BOTH to initialize counter and to salt password!
|
||||
key = make_key(passwd, iv, preset, "wrapper key")
|
||||
m = "incog data"
|
||||
wrap_enc = encrypt_data(salt + enc_seed, key, int(hexlify(iv),16), m)
|
||||
|
||||
return iv+wrap_enc,seed_id,key_id,iv_id,preset
|
||||
|
||||
|
||||
def get_seed(infile,opts,silent=False):
|
||||
def _get_seed(infile,opts,silent=False,seed_id=""):
|
||||
|
||||
ext = get_extension(infile)
|
||||
|
||||
|
|
@ -313,8 +368,9 @@ def get_seed(infile,opts,silent=False):
|
|||
else: msg("No seed source type specified and no file supplied")
|
||||
sys.exit(2)
|
||||
|
||||
seed_id_str = " for seed ID "+seed_id if seed_id else ""
|
||||
if source == "mnemonic":
|
||||
prompt = "Enter mnemonic: "
|
||||
prompt = "Enter mnemonic%s: " % seed_id_str
|
||||
words = get_words(infile,"mnemonic data",prompt,opts)
|
||||
wl = get_default_wordlist()
|
||||
from mmgen.mnemonic import get_seed_from_mnemonic
|
||||
|
|
@ -323,17 +379,17 @@ def get_seed(infile,opts,silent=False):
|
|||
if 'from_brain' not in opts:
|
||||
msg("'--from-brain' parameters must be specified for brainwallet file")
|
||||
sys.exit(2)
|
||||
prompt = "Enter brainwallet passphrase: "
|
||||
prompt = "Enter brainwallet passphrase%s: " % seed_id_str
|
||||
words = get_words(infile,"brainwallet data",prompt,opts)
|
||||
seed = _get_seed_from_brain_passphrase(words,opts)
|
||||
elif source == "seed":
|
||||
prompt = "Enter seed in %s format: " % g.seed_ext
|
||||
prompt = "Enter seed%s in %s format: " % (seed_id_str,g.seed_ext)
|
||||
words = get_words(infile,"seed data",prompt,opts)
|
||||
seed = get_seed_from_seed_data(words)
|
||||
elif source == "wallet":
|
||||
seed = get_seed_from_wallet(infile, opts, silent=silent)
|
||||
elif source == "incognito wallet":
|
||||
h = True if ext == g.incog_hex_ext or 'from_incog_hex' in opts else False
|
||||
h = ext == g.incog_hex_ext or 'from_incog_hex' in opts
|
||||
seed = get_seed_from_incog_wallet(infile, opts, silent=silent, hex_input=h)
|
||||
|
||||
|
||||
|
|
@ -348,10 +404,10 @@ def get_seed(infile,opts,silent=False):
|
|||
|
||||
|
||||
# Repeat if entered data is invalid
|
||||
def get_seed_retry(infile,opts):
|
||||
def get_seed_retry(infile,opts,seed_id=""):
|
||||
silent = False
|
||||
while True:
|
||||
seed = get_seed(infile,opts,silent=silent)
|
||||
seed = _get_seed(infile,opts,silent=silent,seed_id=seed_id)
|
||||
silent = True
|
||||
if seed: return seed
|
||||
|
||||
|
|
@ -385,7 +441,7 @@ def mmgen_encrypt(data,what="data",hash_preset='',opts={}):
|
|||
return salt+iv+enc_d
|
||||
|
||||
|
||||
def mmgen_decrypt(data,what="data",hash_preset='',opts={}):
|
||||
def mmgen_decrypt(data,what="data",hash_preset=""):
|
||||
dstart = salt_len + g.aesctr_iv_len
|
||||
salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:]
|
||||
vmsg("Preparing to decrypt %s" % what)
|
||||
|
|
@ -396,8 +452,14 @@ def mmgen_decrypt(data,what="data",hash_preset='',opts={}):
|
|||
key = make_key(passwd, salt, hp)
|
||||
dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16), what)
|
||||
if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
|
||||
vmsg("Success. Passphrase and hash preset are correct")
|
||||
vmsg("OK")
|
||||
return dec_d[sha256_len+nonce_len:]
|
||||
else:
|
||||
msg("Incorrect passphrase or hash preset")
|
||||
return False
|
||||
|
||||
def mmgen_decrypt_retry(d,what="data"):
|
||||
while True:
|
||||
d_dec = mmgen_decrypt(d,what)
|
||||
if d_dec: return d_dec
|
||||
msg("Trying again...")
|
||||
|
|
|
|||
|
|
@ -41,3 +41,7 @@ def main(progname):
|
|||
sys.stderr.write("\nUser interrupt\n")
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
sys.exit(1)
|
||||
except EOFError:
|
||||
sys.stderr.write("\nEnd of file\n")
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
sys.exit(1)
|
||||
|
|
|
|||
|
|
@ -42,10 +42,10 @@ help_data = {
|
|||
-h, --help Print this help message{}
|
||||
-d, --outdir= d Specify an alternate directory 'd' for output
|
||||
-c, --save-checksum Save address list checksum to file
|
||||
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry{}
|
||||
-e, --echo-passphrase Echo passphrase or mnemonic to screen upon entry
|
||||
-H, --show-hash-presets Show information on available hash presets
|
||||
-K, --no-keyconv Use internal libraries for address generation
|
||||
instead of 'keyconv'
|
||||
-K, --no-keyconv Force use of internal libraries for address gener-
|
||||
ation, even if 'keyconv' is available
|
||||
-l, --seed-len= N Length of seed. Options: {seed_lens}
|
||||
(default: {g.seed_len})
|
||||
-p, --hash-preset= p Use scrypt.hash() parameters from preset 'p' when
|
||||
|
|
@ -63,17 +63,16 @@ help_data = {
|
|||
-X, --from-incog-hex Generate {what} from incognito hexadecimal wallet
|
||||
-G, --from-incog-hidden=f,o,l Generate {what} from incognito data in file
|
||||
'f' at offset 'o', with seed length of 'l'
|
||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
||||
-m, --from-mnemonic Generate {what} from an electrum-like mnemonic
|
||||
-s, --from-seed Generate {what} from a seed in .{g.seed_ext} format
|
||||
""".format(
|
||||
*(
|
||||
(
|
||||
(
|
||||
"\n-A, --no-addresses Print only secret keys, no addresses",
|
||||
"\n-f, --flat-list Produce a flat list of keys suitable for use with" +
|
||||
"\n '{}-txsign'".format(g.proj_name.lower()),
|
||||
"\n-x, --b16 Print secret keys in hexadecimal too"
|
||||
)
|
||||
if what == "keys" else ("","","")),
|
||||
if what == "keys" else ("","")),
|
||||
seed_lens=", ".join([str(i) for i in g.seed_lens]),
|
||||
what=what, g=g
|
||||
),
|
||||
|
|
@ -109,6 +108,13 @@ invocations with that passphrase
|
|||
if what == "keys" else "")
|
||||
}
|
||||
|
||||
wmsg = {
|
||||
'unencrypted_secret_keys': """
|
||||
This program generates secret keys from your {} seed, outputting them in
|
||||
UNENCRYPTED form. Generate only the key(s) you need and guard them carefully.
|
||||
""".format(g.proj_name),
|
||||
}
|
||||
|
||||
opts,cmd_args = parse_opts(sys.argv,help_data)
|
||||
|
||||
if 'show_hash_presets' in opts: show_hash_presets()
|
||||
|
|
@ -129,7 +135,7 @@ elif len(cmd_args) == 2:
|
|||
check_infile(infile)
|
||||
else: usage(help_data)
|
||||
|
||||
addr_idxs = parse_address_list(addr_idx_arg)
|
||||
addr_idxs = parse_addr_idxs(addr_idx_arg)
|
||||
|
||||
if not addr_idxs: sys.exit(2)
|
||||
|
||||
|
|
@ -137,38 +143,44 @@ do_license_msg()
|
|||
|
||||
# Interact with user:
|
||||
if what == "keys" and not g.quiet:
|
||||
confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
|
||||
confirm_or_exit(wmsg['unencrypted_secret_keys'], 'continue')
|
||||
|
||||
# Generate data:
|
||||
|
||||
seed = get_seed_retry(infile,opts)
|
||||
seed_id = make_chksum_8(seed)
|
||||
|
||||
for l in (
|
||||
('flat_list', 'no_addresses'),
|
||||
('flat_list', 'b16'),
|
||||
): warn_incompatible_opts(opts,l)
|
||||
opts['gen_what'] = "a" if what == "addresses" else (
|
||||
"k" if 'no_addresses' in opts else "ka")
|
||||
|
||||
opts['gen_what'] = \
|
||||
["addrs"] if what == "addresses" else (
|
||||
["keys"] if 'no_addresses' in opts else ["addrs","keys"])
|
||||
addr_data = generate_addrs(seed, addr_idxs, opts)
|
||||
addr_data_chksum = make_addr_data_chksum([(a.num,a.addr)
|
||||
for a in addr_data]) if 'addrs' in opts['gen_what'] else ""
|
||||
addr_data_str = format_addr_data(
|
||||
addr_data = generate_addrs(seed, addr_idxs, opts)
|
||||
|
||||
if 'a' in opts['gen_what']:
|
||||
if 'k' in opts['gen_what']:
|
||||
def l(a): return ( a.num, (a.addr,"",a.wif) )
|
||||
keys = True
|
||||
else:
|
||||
def l(a): return ( a.num, (a.addr,) )
|
||||
keys = False
|
||||
addr_data_chksum = make_addr_data_chksum([l(a) for a in addr_data],keys)
|
||||
else:
|
||||
addr_data_chksum = ""
|
||||
|
||||
addr_data_str = format_addr_data(
|
||||
addr_data, addr_data_chksum, seed_id, addr_idxs, opts)
|
||||
|
||||
outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
|
||||
if 'addrs' in opts['gen_what']:
|
||||
qmsg("Checksum for address data %s: %s" % (outfile_base,addr_data_chksum))
|
||||
if 'a' in opts['gen_what']:
|
||||
w = "key-address" if 'k' in opts['gen_what'] else "address"
|
||||
qmsg("Checksum for %s data %s: %s" % (w,outfile_base,addr_data_chksum))
|
||||
if 'save_checksum' in opts:
|
||||
write_to_file(outfile_base+"."+g.addrfile_chksum_ext,
|
||||
addr_data_chksum+"\n",opts,"address data checksum",True,True,False)
|
||||
addr_data_chksum+"\n",opts,"%s data checksum" % w,True,True,False)
|
||||
else:
|
||||
qmsg("This checksum will be used to verify the address file in the future.")
|
||||
qmsg("This checksum will be used to verify the %s file in the future."%w)
|
||||
qmsg("Record it to a safe location.")
|
||||
|
||||
if 'flat_list' in opts and keypress_confirm("Encrypt key list?"):
|
||||
if 'k' in opts['gen_what'] and keypress_confirm("Encrypt key list?"):
|
||||
addr_data_str = mmgen_encrypt(addr_data_str,"key list","",opts)
|
||||
enc_ext = "." + g.mmenc_ext
|
||||
else: enc_ext = ""
|
||||
|
|
@ -178,13 +190,11 @@ if 'stdout' in opts or not sys.stdout.isatty():
|
|||
if enc_ext and sys.stdout.isatty():
|
||||
msg("Cannot write encrypted data to screen. Exiting")
|
||||
sys.exit(2)
|
||||
c = True if (what == "keys" and not g.quiet and sys.stdout.isatty()) else False
|
||||
write_to_stdout(addr_data_str,what,c)
|
||||
write_to_stdout(addr_data_str,what,
|
||||
(what=="keys"and not g.quiet and sys.stdout.isatty()))
|
||||
else:
|
||||
confirm_overwrite = False if g.quiet else True
|
||||
outfile = "%s.%s%s" % (outfile_base, (
|
||||
g.keylist_ext if 'flat_list' in opts else (
|
||||
g.keyfile_ext if opts['gen_what'] == ["keys"] else (
|
||||
g.addrfile_ext if opts['gen_what'] == ["addrs"] else "akeys"))), enc_ext)
|
||||
write_to_file(outfile,addr_data_str,opts,what,confirm_overwrite,True)
|
||||
|
||||
g.keyaddrfile_ext if "ka" in opts['gen_what'] else (
|
||||
g.keyfile_ext if "k" in opts['gen_what'] else
|
||||
g.addrfile_ext)), enc_ext)
|
||||
write_to_file(outfile,addr_data_str,opts,what,not g.quiet,True)
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import sys
|
|||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.util import *
|
||||
from mmgen.tx import connect_to_bitcoind,parse_addrs_file
|
||||
from mmgen.tx import connect_to_bitcoind,parse_addrfile,parse_keyaddr_file
|
||||
|
||||
help_data = {
|
||||
'prog_name': g.prog_name,
|
||||
|
|
@ -32,38 +32,44 @@ help_data = {
|
|||
watching wallet""".format(pnm=g.proj_name),
|
||||
'usage':"[opts] [mmgen address file]",
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-l, --addrlist= f Import the non-mmgen Bitcoin addresses listed in file 'f'
|
||||
-q, --quiet Suppress warnings
|
||||
-r, --rescan Rescan the blockchain. Required if address to import is
|
||||
on the blockchain and has a balance. Rescanning is slow.
|
||||
-h, --help Print this help message
|
||||
-l, --addrlist Address source is a flat list of addresses
|
||||
-k, --keyaddr-file Address source is a key-address file
|
||||
-q, --quiet Suppress warnings
|
||||
-r, --rescan Rescan the blockchain. Required if address to import is
|
||||
on the blockchain and has a balance. Rescanning is slow.
|
||||
"""
|
||||
}
|
||||
|
||||
opts,cmd_args = parse_opts(sys.argv,help_data)
|
||||
|
||||
if len(cmd_args) != 1 and not 'addrlist' in opts:
|
||||
msg("You must specify an mmgen address list (and/or non-mmgen addresses with the '--addrlist' option)")
|
||||
sys.exit(1)
|
||||
|
||||
if cmd_args:
|
||||
check_infile(cmd_args[0])
|
||||
seed_id,addr_data = parse_addrs_file(cmd_args[0])
|
||||
if len(cmd_args) == 1:
|
||||
infile = cmd_args[0]
|
||||
check_infile(infile)
|
||||
if 'addrlist' in opts:
|
||||
lines = get_lines_from_file(infile,"non-{} addresses".format(g.proj_name),
|
||||
trim_comments=True)
|
||||
addr_list = [(None,l) for l in lines]
|
||||
seed_id = ""
|
||||
else:
|
||||
addr_data = {}
|
||||
pf = parse_keyaddr_file if 'keyaddr_file' in opts else parse_addrfile
|
||||
pf(infile,addr_data)
|
||||
seed_id = addr_data.keys()[0]
|
||||
e = addr_data[seed_id]
|
||||
addr_list = [(k,e[k][0],e[k][1]) for k in e.keys()]
|
||||
else:
|
||||
seed_id,addr_data = "",[]
|
||||
|
||||
if 'addrlist' in opts:
|
||||
lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
|
||||
trim_comments=True)
|
||||
addr_data += [(None,l) for l in lines]
|
||||
msg_r("You must specify an mmgen address list (or a list of ")
|
||||
msg("non-%s addresses with\nthe '--addrlist' option)" % g.proj_name)
|
||||
sys.exit(1)
|
||||
|
||||
from mmgen.bitcoin import verify_addr
|
||||
qmsg_r("Validating addresses...")
|
||||
for i in addr_data:
|
||||
for n,i in enumerate(addr_list,1):
|
||||
if not verify_addr(i[1],verbose=True):
|
||||
msg("%s: invalid address" % i)
|
||||
sys.exit(2)
|
||||
qmsg("OK")
|
||||
qmsg("OK. %s addresses%s" % (n," from seed ID "+seed_id if seed_id else ""))
|
||||
|
||||
import mmgen.config as g
|
||||
g.http_timeout = 3600
|
||||
|
|
@ -94,8 +100,9 @@ def import_address(addr,label,rescan):
|
|||
err_flag = True
|
||||
|
||||
|
||||
w1 = len(str(len(addr_data))) * 2 + 2
|
||||
w2 = len(str(max([i[0] for i in addr_data if i[0]]))) + 12
|
||||
w1 = len(str(len(addr_list))) * 2 + 2
|
||||
w2 = "" if 'addrlist' in opts else \
|
||||
len(str(max([i[0] for i in addr_list if i[0]]))) + 12 \
|
||||
|
||||
if "rescan" in opts:
|
||||
import threading
|
||||
|
|
@ -105,10 +112,9 @@ else:
|
|||
msg_fmt = "\r%-" + str(w1) + "s %-34s %-" + str(w2) + "s"
|
||||
|
||||
msg("Importing addresses")
|
||||
for n,i in enumerate(addr_data):
|
||||
for n,i in enumerate(addr_list):
|
||||
if i[0]:
|
||||
comment = " " + i[2] if len(i) == 3 else ""
|
||||
label = "%s:%s%s" % (seed_id,i[0],comment)
|
||||
label = "%s:%s%s" % (seed_id,i[0], (" "+i[2] if i[2] else ""))
|
||||
else: label = "non-mmgen"
|
||||
|
||||
if "rescan" in opts:
|
||||
|
|
@ -123,7 +129,7 @@ for n,i in enumerate(addr_data):
|
|||
elapsed = int(time.time() - start)
|
||||
msg_r(msg_fmt % (
|
||||
secs_to_hms(elapsed),
|
||||
("%s/%s:" % (n+1,len(addr_data))),
|
||||
("%s/%s:" % (n+1,len(addr_list))),
|
||||
i[1], "(" + label + ")"
|
||||
)
|
||||
)
|
||||
|
|
@ -134,7 +140,7 @@ for n,i in enumerate(addr_data):
|
|||
break
|
||||
else:
|
||||
import_address(i[1],label,rescan=False)
|
||||
msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_data))),
|
||||
msg_r(msg_fmt % (("%s/%s:" % (n+1,len(addr_list))),
|
||||
i[1], "(" + label + ")"))
|
||||
if err_flag: msg("\nImport failed"); sys.exit(2)
|
||||
msg(" - OK")
|
||||
|
|
|
|||
|
|
@ -1671,7 +1671,7 @@ data = "\n".join(data) + "\n"
|
|||
|
||||
# Output data
|
||||
if 'stdout' in opts or not sys.stdout.isatty():
|
||||
c = False if ('addrs' in opts or not sys.stdout.isatty()) else True
|
||||
write_to_stdout(data,"secret keys",c)
|
||||
conf = not ('addrs' in opts or not sys.stdout.isatty())
|
||||
write_to_stdout(data,"secret keys",conf)
|
||||
else:
|
||||
write_walletdat_dump_to_file(wallet_id, data, len_arg, ext, what, opts)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ import mmgen.config as g
|
|||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.util import msg, msg_r, keypress_confirm
|
||||
|
||||
help_data = {
|
||||
'prog_name': g.prog_name,
|
||||
|
|
@ -43,6 +42,7 @@ help_data = {
|
|||
-i, --info Display unspent outputs and exit
|
||||
-q, --quiet Suppress warnings; overwrite files without
|
||||
prompting
|
||||
-v, --verbose Produce more verbose output
|
||||
""".format(g=g),
|
||||
'notes': """
|
||||
|
||||
|
|
@ -60,40 +60,300 @@ with no amount on the command line.
|
|||
""".format(g=g,pnm=g.proj_name)
|
||||
}
|
||||
|
||||
wmsg = {
|
||||
'too_many_acct_addresses': """
|
||||
ERROR: More than one address found for account: "%s".
|
||||
Your "wallet.dat" file appears to have been altered by a non-{pnm} program.
|
||||
Please restore your tracking wallet from a backup or create a new one and
|
||||
re-import your addresses.
|
||||
""".strip().format(pnm=g.proj_name),
|
||||
'addr_in_addrfile_only': """
|
||||
Warning: output address {mmgenaddr} is not in the tracking wallet, which means
|
||||
its balance will not be tracked. You're strongly advised to import the address
|
||||
into your tracking wallet before broadcasting this transaction.
|
||||
""".strip(),
|
||||
'addr_not_found': """
|
||||
No data for MMgen address {mmgenaddr} could be found in either the tracking
|
||||
wallet or the supplied address file. Please import this address into your
|
||||
tracking wallet, or supply an address file for it on the command line.
|
||||
""".strip(),
|
||||
'addr_not_found_no_addrfile': """
|
||||
No data for MMgen address {mmgenaddr} could be found in the tracking wallet.
|
||||
Please import this address into your tracking wallet or supply an address file
|
||||
for it on the command line.
|
||||
""".strip(),
|
||||
'no_spendable_outputs': """
|
||||
No spendable outputs found! Import addresses with balances into your
|
||||
watch-only wallet using '{pnm}-addrimport' and then re-run this program.
|
||||
""".strip().format(pnm=g.proj_name.lower()),
|
||||
'mixed_inputs': """
|
||||
NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs, which
|
||||
makes the signing process more complicated. When signing the transaction, keys
|
||||
for the non-{pnm} inputs must be supplied to '{pnl}-txsign' in a file with the
|
||||
'--keys-from-file' option.
|
||||
|
||||
Selected mmgen inputs: %s
|
||||
""".strip().format(pnm=g.proj_name,pnl=g.proj_name.lower()),
|
||||
'not_enough_btc': """
|
||||
Not enough BTC in the inputs for this transaction (%s BTC)
|
||||
""".strip(),
|
||||
'throwaway_change': """
|
||||
ERROR: This transaction produces change (%s BTC); however, no change address
|
||||
was specified.
|
||||
""".strip(),
|
||||
}
|
||||
|
||||
def format_unspent_outputs_for_printing(out,sort_info,total):
|
||||
|
||||
pfs = " %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s"
|
||||
pout = [pfs % ("Num","TX id,Vout","Address","MMgen ID",
|
||||
"Amount (BTC)","Conf.","Age (days)", "Comment")]
|
||||
|
||||
for n,i in enumerate(out):
|
||||
addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address
|
||||
tx = " " * 63 + "=" \
|
||||
if i.skip == "txid" and "grouped" in sort_info else str(i.txid)
|
||||
|
||||
s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,
|
||||
i.mmid,i.amt,i.confirmations,i.days,i.label)
|
||||
pout.append(s.rstrip())
|
||||
|
||||
return \
|
||||
"Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format(
|
||||
make_timestr(), " ".join(sort_info), "\n".join(pout), total
|
||||
)
|
||||
|
||||
|
||||
def sort_and_view(unspent,opts):
|
||||
|
||||
def s_amt(i): return i.amount
|
||||
def s_txid(i): return "%s %03s" % (i.txid,i.vout)
|
||||
def s_addr(i): return i.address
|
||||
def s_age(i): return i.confirmations
|
||||
def s_mmgen(i):
|
||||
m = parse_mmgen_label(i.account)[0]
|
||||
if m: return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *m.split(":"))
|
||||
else: return "G" + i.account
|
||||
|
||||
sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True
|
||||
unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default
|
||||
|
||||
total = trim_exponent(sum([i.amount for i in unspent]))
|
||||
max_acct_len = max([len(i.account) for i in unspent])
|
||||
|
||||
hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s"
|
||||
options_msg = """
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
|
||||
Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
||||
""".strip()
|
||||
prompt = \
|
||||
"('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
|
||||
|
||||
from copy import deepcopy
|
||||
from mmgen.term import get_terminal_size
|
||||
|
||||
write_to_file_msg = ""
|
||||
msg("")
|
||||
|
||||
while True:
|
||||
cols = get_terminal_size()[0]
|
||||
if cols < g.min_screen_width:
|
||||
msg("%s-txcreate requires a screen at least %s characters wide" %
|
||||
(g.proj_name.lower(),g.min_screen_width))
|
||||
sys.exit(2)
|
||||
|
||||
addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
|
||||
acct_w = min(max_acct_len, max(24,int(addr_w-10)))
|
||||
btaddr_w = addr_w - acct_w - 1
|
||||
tx_w = max(11,min(64, cols-addr_w-32))
|
||||
txdots = "..." if tx_w < 64 else ""
|
||||
fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s"
|
||||
table_hdr = fs % ("Num","TX id Vout","","Address","Amount (BTC)",
|
||||
"Age(d)" if show_days else "Conf.")
|
||||
|
||||
unsp = deepcopy(unspent)
|
||||
for i in unsp: i.skip = ""
|
||||
if group and (sort == "address" or sort == "txid"):
|
||||
for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
|
||||
if sort == "address" and a.address == b.address: b.skip = "addr"
|
||||
elif sort == "txid" and a.txid == b.txid: b.skip = "txid"
|
||||
|
||||
for i in unsp:
|
||||
amt = str(trim_exponent(i.amount))
|
||||
lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
|
||||
i.amt = " "*lfill + amt
|
||||
i.days = int(i.confirmations * g.mins_per_block / (60*24))
|
||||
i.age = i.days if show_days else i.confirmations
|
||||
i.mmid,i.label = parse_mmgen_label(i.account)
|
||||
|
||||
if i.skip == "addr":
|
||||
i.addr = "|" + "." * 33
|
||||
else:
|
||||
if show_mmaddr:
|
||||
dots = ".." if btaddr_w < len(i.address) else ""
|
||||
i.addr = "%s%s %s" % (
|
||||
i.address[:btaddr_w-len(dots)],
|
||||
dots,
|
||||
i.account[:acct_w])
|
||||
else:
|
||||
i.addr = i.address
|
||||
|
||||
i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
|
||||
else i.txid[:tx_w-len(txdots)]+txdots
|
||||
|
||||
sort_info = ["reverse"] if reverse else []
|
||||
sort_info.append(sort if sort else "unsorted")
|
||||
if group and (sort == "address" or sort == "txid"):
|
||||
sort_info.append("grouped")
|
||||
|
||||
out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
|
||||
out += [fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.age)
|
||||
for n,i in enumerate(unsp)]
|
||||
|
||||
msg("\n".join(out) +"\n\n" + write_to_file_msg + options_msg)
|
||||
write_to_file_msg = ""
|
||||
|
||||
skip_prompt = False
|
||||
|
||||
while True:
|
||||
reply = get_char(prompt, immed_chars="atDdAMrgmeqpvw")
|
||||
|
||||
if reply == 'a': unspent.sort(key=s_amt); sort = "amount"
|
||||
elif reply == 't': unspent.sort(key=s_txid); sort = "txid"
|
||||
elif reply == 'D': show_days = not show_days
|
||||
elif reply == 'd': unspent.sort(key=s_addr); sort = "address"
|
||||
elif reply == 'A': unspent.sort(key=s_age); sort = "age"
|
||||
elif reply == 'M':
|
||||
unspent.sort(key=s_mmgen); sort = "mmgen"
|
||||
show_mmaddr = True
|
||||
elif reply == 'r':
|
||||
unspent.reverse()
|
||||
reverse = not reverse
|
||||
elif reply == 'g': group = not group
|
||||
elif reply == 'm': show_mmaddr = not show_mmaddr
|
||||
elif reply == 'e': pass
|
||||
elif reply == 'q': pass
|
||||
elif reply == 'p':
|
||||
d = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
of = "listunspent[%s].out" % ",".join(sort_info)
|
||||
write_to_file(of, d, opts,"",False,False)
|
||||
write_to_file_msg = "Data written to '%s'\n\n" % of
|
||||
elif reply == 'v':
|
||||
do_pager("\n".join(out))
|
||||
continue
|
||||
elif reply == 'w':
|
||||
data = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
do_pager(data)
|
||||
continue
|
||||
else:
|
||||
msg("\nInvalid input")
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
msg("\n")
|
||||
if reply == 'q': break
|
||||
|
||||
return tuple(unspent)
|
||||
|
||||
|
||||
def select_outputs(unspent,prompt):
|
||||
|
||||
while True:
|
||||
reply = my_raw_input(prompt).strip()
|
||||
|
||||
if not reply: continue
|
||||
|
||||
selected = parse_addr_idxs(reply,sep=None)
|
||||
|
||||
if not selected: continue
|
||||
|
||||
if selected[-1] > len(unspent):
|
||||
msg("Inputs must be less than %s" % len(unspent))
|
||||
continue
|
||||
|
||||
return selected
|
||||
|
||||
|
||||
def get_acct_data_from_wallet(c,acct_data):
|
||||
# acct_data is global object initialized by caller
|
||||
vmsg_r("Getting account data from wallet...")
|
||||
accts,i = c.listaccounts(minconf=0,includeWatchonly=True),0
|
||||
for acct in accts:
|
||||
ma,comment = parse_mmgen_label(acct)
|
||||
if ma:
|
||||
i += 1
|
||||
addrlist = c.getaddressesbyaccount(acct)
|
||||
if len(addrlist) != 1:
|
||||
msg(wmsg['too_many_acct_addresses'] % acct)
|
||||
sys.exit(2)
|
||||
seed_id,idx = ma.split(":")
|
||||
if seed_id not in acct_data:
|
||||
acct_data[seed_id] = {}
|
||||
acct_data[seed_id][idx] = (addrlist[0],comment)
|
||||
vmsg("%s %s addresses found, %s accounts total" % (i,g.proj_name,len(accts)))
|
||||
|
||||
def mmaddr2btcaddr_unspent(unspent,mmaddr):
|
||||
vmsg_r("Searching for {g.proj_name} address {m} in wallet...".format(g=g,m=mmaddr))
|
||||
m = [u for u in unspent if u.account.split()[0] == mmaddr]
|
||||
if len(m) == 0:
|
||||
vmsg("not found")
|
||||
return "",""
|
||||
elif len(m) > 1:
|
||||
msg(wmsg['too_many_acct_addresses'] % acct); sys.exit(2)
|
||||
else:
|
||||
vmsg("success (%s)" % m[0].address)
|
||||
return m[0].address, split2(m[0].account)[1]
|
||||
sys.exit()
|
||||
|
||||
|
||||
def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map):
|
||||
# assume mmaddr has already been checked
|
||||
if not acct_data: get_acct_data_from_wallet(c,acct_data)
|
||||
btcaddr,comment = mmaddr2btcaddr_addrdata(mmaddr,acct_data,"wallet")
|
||||
# btcaddr,comment = mmaddr2btcaddr_unspent(us,mmaddr)
|
||||
if not btcaddr:
|
||||
if addr_data:
|
||||
btcaddr,comment = mmaddr2btcaddr_addrdata(mmaddr,addr_data,"addr file")
|
||||
if btcaddr:
|
||||
msg(wmsg['addr_in_addrfile_only'].format(mmgenaddr=mmaddr))
|
||||
if not keypress_confirm("Continue anyway?"):
|
||||
sys.exit(1)
|
||||
else:
|
||||
msg(wmsg['addr_not_found'].format(mmgenaddr=mmaddr))
|
||||
sys.exit(2)
|
||||
else:
|
||||
msg(wmsg['addr_not_found_no_addrfile'].format(mmgenaddr=mmaddr))
|
||||
sys.exit(2)
|
||||
|
||||
b2m_map[btcaddr] = mmaddr,comment
|
||||
return btcaddr
|
||||
|
||||
|
||||
opts,cmd_args = parse_opts(sys.argv,help_data)
|
||||
|
||||
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
|
||||
|
||||
if 'comment_file' in opts:
|
||||
comment = get_tx_comment_from_file(opts['comment_file'])
|
||||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
if not 'info' in opts:
|
||||
do_license_msg(immed=True)
|
||||
|
||||
tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
|
||||
tx_out,addr_data,b2m_map,acct_data,change_addr = {},{},{},{},""
|
||||
|
||||
addrfiles = [a for a in cmd_args if get_extension(a) == g.addrfile_ext]
|
||||
cmd_args = set(cmd_args) - set(addrfiles)
|
||||
|
||||
for a in addrfiles:
|
||||
check_infile(a)
|
||||
addr_data.append(parse_addrs_file(a))
|
||||
|
||||
def mmaddr2btcaddr(c,mmaddr,acct_data,addr_data,b2m_map):
|
||||
# assume mmaddr has already been checked
|
||||
btcaddr,label = mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data)
|
||||
if not btcaddr:
|
||||
if addr_data:
|
||||
btcaddr,label = mmaddr2btcaddr_addrfile(mmaddr,addr_data)
|
||||
else:
|
||||
msg(txmsg['addrfile_no_data_msg'] % mmaddr)
|
||||
sys.exit(2)
|
||||
|
||||
b2m_map[btcaddr] = mmaddr,label
|
||||
return btcaddr
|
||||
parse_addrfile(a,addr_data)
|
||||
|
||||
for a in cmd_args:
|
||||
if "," in a:
|
||||
a1,a2 = a.split(",")
|
||||
a1,a2 = split2(a,",")
|
||||
if is_btc_addr(a1):
|
||||
btcaddr = a1
|
||||
elif is_mmgen_addr(a1):
|
||||
|
|
@ -131,16 +391,13 @@ if not 'info' in opts:
|
|||
|
||||
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
|
||||
|
||||
#write_to_file("bogus_unspent.json", repr(us), opts); sys.exit()
|
||||
|
||||
#if False:
|
||||
if g.bogus_wallet_data:
|
||||
import mmgen.rpc
|
||||
if g.bogus_wallet_data: # for debugging purposes only
|
||||
us = eval(get_data_from_file(g.bogus_wallet_data))
|
||||
else:
|
||||
us = c.listunspent()
|
||||
# write_to_file("bogus_unspent.json", repr(us), opts); sys.exit()
|
||||
|
||||
if not us: msg(txmsg['no_spendable_outputs']); sys.exit(2)
|
||||
if not us: msg(wmsg['no_spendable_outputs']); sys.exit(2)
|
||||
|
||||
unspent = sort_and_view(us,opts)
|
||||
|
||||
|
|
@ -165,7 +422,7 @@ while True:
|
|||
mmaddrs.discard("")
|
||||
|
||||
if mmaddrs and len(mmaddrs) < len(sel_unspent):
|
||||
msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
|
||||
msg(wmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
|
||||
if not keypress_confirm("Accept?"):
|
||||
continue
|
||||
|
||||
|
|
@ -177,10 +434,10 @@ while True:
|
|||
if keypress_confirm(prompt,default_yes=True):
|
||||
break
|
||||
else:
|
||||
msg(txmsg['not_enough_btc'] % change)
|
||||
msg(wmsg['not_enough_btc'] % change)
|
||||
|
||||
if change > 0 and not change_addr:
|
||||
msg(txmsg['throwaway_change'] % change)
|
||||
msg(wmsg['throwaway_change'] % change)
|
||||
sys.exit(2)
|
||||
|
||||
if change_addr in tx_out and not change:
|
||||
|
|
@ -198,8 +455,6 @@ if g.debug:
|
|||
print "tx_out:", repr(tx_out)
|
||||
|
||||
if 'comment_file' in opts:
|
||||
comment = get_tx_comment_from_file(opts['comment_file'])
|
||||
if comment == False: sys.exit(2)
|
||||
if keypress_confirm("Edit comment?",False):
|
||||
comment = get_tx_comment_from_user(comment)
|
||||
else:
|
||||
|
|
@ -219,7 +474,7 @@ metadata = tx_id, amt, make_timestamp()
|
|||
|
||||
if reply and reply in "YyVv":
|
||||
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,
|
||||
comment,metadata,True if reply in "Vv" else False)
|
||||
comment,metadata,reply in "Vv")
|
||||
|
||||
prompt = "Save transaction?"
|
||||
if keypress_confirm(prompt,default_yes=True):
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ do_license_msg()
|
|||
|
||||
tx_data = get_lines_from_file(infile,"signed transaction data")
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_data(tx_data,infile)
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile)
|
||||
|
||||
qmsg("Signed transaction file '%s' is valid" % infile)
|
||||
|
||||
|
|
@ -60,8 +60,7 @@ c = connect_to_bitcoind()
|
|||
prompt = "View transaction data? (y)es, (N)o, (v)iew in pager"
|
||||
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
|
||||
if reply and reply in "YyVv":
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,
|
||||
pager=True if reply in "Vv" else False)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,reply in "Vv")
|
||||
|
||||
if keypress_confirm("Edit transaction comment?"):
|
||||
comment = get_tx_comment_from_user(comment)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ import mmgen.config as g
|
|||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.util import msg,qmsg
|
||||
|
||||
help_data = {
|
||||
'prog_name': g.prog_name,
|
||||
|
|
@ -39,16 +38,18 @@ help_data = {
|
|||
-i, --info Display information about the transaction and exit
|
||||
-I, --tx-id Display transaction ID and exit
|
||||
-k, --keys-from-file= f Provide additional keys for non-{pnm} addresses
|
||||
-K, --all-keys-from-file=f Like '-k', only use the keyfile as key source
|
||||
for ALL inputs, including {pnm} ones. Can be used
|
||||
for online signing without an {pnm} seed source.
|
||||
{pnm}-to-BTC mappings can optionally be verified
|
||||
using address file(s) listed on the command line
|
||||
-K, --no-keyconv Force use of internal libraries for address gener-
|
||||
ation, even if 'keyconv' is available
|
||||
-M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
|
||||
address file (output of '{pnl}-keygen'). Permits
|
||||
online signing without an {pnm} seed source.
|
||||
The key-address file is also used to verify
|
||||
{pnm}-to-BTC mappings, so its checksum should
|
||||
be recorded by the user.
|
||||
-P, --passwd-file= f Get MMGen wallet or bitcoind passphrase from file 'f'
|
||||
-q, --quiet Suppress warnings; overwrite files without
|
||||
prompting
|
||||
-v, --verbose Produce more verbose output
|
||||
-V, --skip-key-preverify Skip optional key pre-verification step
|
||||
-b, --from-brain= l,p Generate keys from a user-created password,
|
||||
i.e. a "brainwallet", using seed length 'l' and
|
||||
hash preset 'p'
|
||||
|
|
@ -57,9 +58,10 @@ help_data = {
|
|||
-X, --from-incog-hex Generate keys from an incognito hexadecimal wallet
|
||||
-G, --from-incog-hidden= f,o,l Generate keys from incognito data in file
|
||||
'f' at offset 'o', with seed length of 'l'
|
||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
||||
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
|
||||
-s, --from-seed Generate keys from a seed in .{g.seed_ext} format
|
||||
""".format(g=g,pnm=g.proj_name),
|
||||
""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower()),
|
||||
'notes': """
|
||||
|
||||
Transactions with either {pnm} or non-{pnm} input addresses may be signed.
|
||||
|
|
@ -89,17 +91,204 @@ Seed data supplied in files must have the following extensions:
|
|||
""".format(g=g,pnm=g.proj_name,pnl=g.proj_name.lower())
|
||||
}
|
||||
|
||||
wmsg = {
|
||||
'mm2btc_mapping_error': """
|
||||
MMGen -> BTC address mappings differ!
|
||||
From %-18s %s -> %s
|
||||
From %-18s %s -> %s
|
||||
""".strip(),
|
||||
'removed_dups': """
|
||||
Removed %s duplicate wif key%s from keylist (also in {pnm} key-address file
|
||||
""".strip().format(pnm=g.proj_name),
|
||||
}
|
||||
|
||||
def get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts):
|
||||
|
||||
if seed_id in saved_seeds.keys():
|
||||
return saved_seeds[seed_id]
|
||||
|
||||
from mmgen.crypto import get_seed_retry
|
||||
|
||||
while True:
|
||||
if infiles:
|
||||
seed = get_seed_retry(infiles.pop(0),opts)
|
||||
elif "from_brain" in opts or "from_mnemonic" in opts \
|
||||
or "from_seed" in opts or "from_incog" in opts:
|
||||
qmsg("Need seed data for seed ID %s" % seed_id)
|
||||
seed = get_seed_retry("",opts,seed_id)
|
||||
msg("User input produced seed ID %s" % make_chksum_8(seed))
|
||||
else:
|
||||
msg("ERROR: No seed source found for seed ID: %s" % seed_id)
|
||||
sys.exit(2)
|
||||
|
||||
sid = make_chksum_8(seed)
|
||||
saved_seeds[sid] = seed
|
||||
|
||||
if sid == seed_id: return seed
|
||||
|
||||
|
||||
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds,opts):
|
||||
|
||||
seed_ids = set([i[:8] for i in mmgen_addrs])
|
||||
vmsg("Need seed%s: %s" % (suf(seed_ids,"k")," ".join(seed_ids)))
|
||||
d = []
|
||||
|
||||
from mmgen.addr import generate_addrs
|
||||
for seed_id in seed_ids:
|
||||
# Returns only if seed is found
|
||||
seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts)
|
||||
addr_nums = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
|
||||
# num sec wif addr
|
||||
d += [("{}:{}".format(seed_id,r.num),r.addr,r.wif)
|
||||
for r in generate_addrs(seed,addr_nums,{'gen_what':"ka"},seed_id)]
|
||||
return d
|
||||
|
||||
|
||||
def sign_transaction(c,tx_hex,tx_num_str,sig_data,keys=None):
|
||||
|
||||
if keys:
|
||||
qmsg("Passing %s key%s to bitcoind" % (len(keys),suf(keys,"k")))
|
||||
if g.debug: print "Keys:\n %s" % "\n ".join(keys)
|
||||
|
||||
msg_r("Signing transaction{}...".format(tx_num_str))
|
||||
from mmgen.rpc import exceptions
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
|
||||
except exceptions.InvalidAddressOrKey:
|
||||
msg("failed\nInvalid address or key")
|
||||
sys.exit(3)
|
||||
|
||||
return sig_tx
|
||||
|
||||
|
||||
def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys,opts):
|
||||
|
||||
try:
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
except:
|
||||
from mmgen.rpc import exceptions
|
||||
msg("Using keys in wallet.dat as per user request")
|
||||
prompt = "Enter passphrase for bitcoind wallet: "
|
||||
while True:
|
||||
passwd = get_bitcoind_passphrase(prompt,opts)
|
||||
|
||||
try:
|
||||
c.walletpassphrase(passwd, 9999)
|
||||
except exceptions.WalletPassphraseIncorrect:
|
||||
msg("Passphrase incorrect")
|
||||
else:
|
||||
msg("Passphrase OK"); break
|
||||
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
|
||||
msg("Locking wallet")
|
||||
try:
|
||||
c.walletlock()
|
||||
except:
|
||||
msg("Failed to lock wallet")
|
||||
|
||||
return sig_tx
|
||||
|
||||
|
||||
def check_maps_from_seeds(maplist,label,infiles,saved_seeds,opts,return_keys=False):
|
||||
|
||||
if not maplist: return []
|
||||
qmsg("Checking MMGen -> BTC address mappings for %ss (from seeds)" % label)
|
||||
d = get_keys_for_mmgen_addrs(maplist.keys(),infiles,saved_seeds,opts)
|
||||
# 0=mmaddr 1=addr 2=wif
|
||||
m = dict([(e[0],e[1]) for e in d])
|
||||
for a,b in zip(sorted(m),sorted(maplist)):
|
||||
if a != b:
|
||||
al,bl = "generated seed:","tx file:"
|
||||
msg(wmsg['mm2btc_mapping_error'] % (al,a,m[a],bl,b,maplist[b]))
|
||||
sys.exit(3)
|
||||
if return_keys:
|
||||
ret = [e[2] for e in d]
|
||||
vmsg("Added %s wif key%s from seeds" % (len(ret),suf(ret,"k")))
|
||||
return ret
|
||||
|
||||
def missing_keys_errormsg(addrs):
|
||||
print """
|
||||
A key file must be supplied (or use the '--use-wallet-dat' option)
|
||||
for the following non-{} address{}:\n {}""".format(
|
||||
g.proj_name,suf(addrs,"a"),"\n ".join(addrs)).strip()
|
||||
|
||||
|
||||
def parse_mmgen_keyaddr_file(opts):
|
||||
adata = {}
|
||||
parse_keyaddr_file(opts['mmgen_keys_from_file'],adata)
|
||||
for sid in adata.keys(): # one seed id, one loop
|
||||
idxs = adata[sid]
|
||||
count = len(idxs.keys())
|
||||
vmsg("Found %s wif key%s for seed ID %s" % (count,suf(count,"k"),sid))
|
||||
# idx: (0=addr, 1=comment 2=wif) -> mmaddr: (0=addr, 1=wif)
|
||||
return dict([("{}:{}".format(sid,k),(idxs[k][0],idxs[k][2]))
|
||||
for k in idxs.keys()])
|
||||
|
||||
|
||||
def parse_keylist(opts,from_file):
|
||||
fn = opts['keys_from_file']
|
||||
d = get_data_from_file(fn,"non-%s keylist" % g.proj_name)
|
||||
enc_ext = get_extension(fn) == g.mmenc_ext
|
||||
if enc_ext or not is_utf8(d):
|
||||
if not enc_ext: qmsg("Keylist file appears to be encrypted")
|
||||
from crypto import mmgen_decrypt_retry
|
||||
d = mmgen_decrypt_retry(d,"encrypted keylist")
|
||||
# Check for duplication with key-address file
|
||||
keys_all = set(remove_comments(d.split("\n")))
|
||||
d = from_file['mmdata']
|
||||
kawifs = [d[k][1] for k in d.keys()]
|
||||
keys = [k for k in keys_all if k not in kawifs]
|
||||
removed = len(keys_all) - len(keys)
|
||||
if removed: vmsg(wmsg['removed_dups'] % (removed,suf(removed,"k")))
|
||||
addrs = []
|
||||
wif2addr_f = get_wif2addr_f()
|
||||
for n,k in enumerate(keys,1):
|
||||
qmsg_r("\rGenerating addresses from keylist: %s/%s" % (n,len(keys)))
|
||||
addrs.append(wif2addr_f(k))
|
||||
qmsg("\rGenerated addresses from keylist: %s/%s " % (n,len(keys)))
|
||||
|
||||
return dict(zip(addrs,keys))
|
||||
|
||||
|
||||
# Check inputs and outputs maps against key-address file, deleting entries:
|
||||
def check_maps_from_kafile(imap,what,kadata,return_keys=False):
|
||||
qmsg("Checking MMGen -> BTC address mappings for %ss (from key-address file)" % what)
|
||||
ret = []
|
||||
for k in imap.keys():
|
||||
if k in kadata.keys():
|
||||
if kadata[k][0] == imap[k]:
|
||||
del imap[k]
|
||||
ret += [kadata[k][1]]
|
||||
else:
|
||||
kl,il = "key-address file:","tx file:"
|
||||
msg(wmsg['mm2btc_mapping_error']%(kl,k,kadata[k][0],il,k,imap[k]))
|
||||
sys.exit(2)
|
||||
if ret: vmsg("Removed %s address%s from %ss map" % (len(ret),suf(ret,"a"),what))
|
||||
if return_keys:
|
||||
vmsg("Added %s wif key%s from %ss map" % (len(ret),suf(ret,"k"),what))
|
||||
return ret
|
||||
|
||||
|
||||
def get_keys_from_keylist(kldata,other_addrs):
|
||||
ret = []
|
||||
for addr in other_addrs[:]:
|
||||
if addr in kldata.keys():
|
||||
ret += [kldata[addr]]
|
||||
other_addrs.remove(addr)
|
||||
vmsg("Added %s wif key%s from user-supplied keylist" %
|
||||
(len(ret),suf(ret,"k")))
|
||||
return ret
|
||||
|
||||
|
||||
opts,infiles = parse_opts(sys.argv,help_data)
|
||||
|
||||
for l in (
|
||||
('tx_id', 'info'),
|
||||
('keys_from_file','all_keys_from_file')
|
||||
('tx_id', 'info')
|
||||
): warn_incompatible_opts(opts,l)
|
||||
|
||||
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
|
||||
opts['from_incog'] = True
|
||||
if 'all_keys_from_file' in opts:
|
||||
opts['keys_from_file'] = opts['all_keys_from_file']
|
||||
|
||||
if not infiles: usage(help_data)
|
||||
for i in infiles: check_infile(i)
|
||||
|
|
@ -108,24 +297,15 @@ c = connect_to_bitcoind()
|
|||
|
||||
saved_seeds = {}
|
||||
tx_files = [i for i in set(infiles) if get_extension(i) == g.rawtx_ext]
|
||||
addrfiles = [a for a in set(infiles) if get_extension(a) == g.addrfile_ext]
|
||||
seed_files = list(set(infiles) - set(tx_files) - set(addrfiles))
|
||||
seed_files = list(set(infiles) - set(tx_files))
|
||||
|
||||
if not "info" in opts: do_license_msg(immed=True)
|
||||
|
||||
from_file = { 'mmdata':{}, 'kldata':{} }
|
||||
if 'mmgen_keys_from_file' in opts:
|
||||
from_file['mmdata'] = parse_mmgen_keyaddr_file(opts) or {}
|
||||
if 'keys_from_file' in opts:
|
||||
fn = opts['keys_from_file']
|
||||
d = get_data_from_file(fn,"keylist")
|
||||
if get_extension(fn) == g.mmenc_ext or not \
|
||||
is_btc_key(remove_comments(d.split("\n"))[0][:55]):
|
||||
qmsg("Keylist appears to be encrypted")
|
||||
from mmgen.crypto import mmgen_decrypt
|
||||
while True:
|
||||
d_dec = mmgen_decrypt(d,"encrypted keylist","",opts)
|
||||
if d_dec: d = d_dec; break
|
||||
msg("Trying again...")
|
||||
keys_from_file = remove_comments(d.split("\n"))
|
||||
else: keys_from_file = []
|
||||
from_file['kldata'] = parse_keylist(opts,from_file) or {}
|
||||
|
||||
tx_num_str = ""
|
||||
for tx_num,tx_file in enumerate(tx_files,1):
|
||||
|
|
@ -136,7 +316,7 @@ for tx_num,tx_file in enumerate(tx_files,1):
|
|||
m = "" if 'tx_id' in opts else "transaction data"
|
||||
tx_data = get_lines_from_file(tx_file,m)
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_data(tx_data,tx_file)
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,tx_file)
|
||||
qmsg("Successfully opened transaction file '%s'" % tx_file)
|
||||
|
||||
if 'tx_id' in opts:
|
||||
|
|
@ -147,56 +327,47 @@ for tx_num,tx_file in enumerate(tx_files,1):
|
|||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata)
|
||||
sys.exit(0)
|
||||
|
||||
# Are inputs mmgen addresses?
|
||||
mmgen_inputs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
|
||||
other_inputs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
|
||||
|
||||
if 'all_keys_from_file' in opts: other_inputs = inputs_data
|
||||
|
||||
keys = keys_from_file
|
||||
|
||||
if other_inputs and not keys and not 'use_wallet_dat' in opts:
|
||||
missing_keys_errormsg(other_inputs)
|
||||
sys.exit(2)
|
||||
|
||||
if other_inputs and keys and not 'skip_key_preverify' in opts:
|
||||
addrs = [i['address'] for i in other_inputs]
|
||||
mm_inputs = mmgen_inputs if 'all_keys_from_file' in opts else []
|
||||
preverify_keys(addrs, keys, mm_inputs)
|
||||
opts['skip_key_preverify'] = True
|
||||
|
||||
if 'all_keys_from_file' in opts:
|
||||
if addrfiles:
|
||||
check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles)
|
||||
else:
|
||||
confirm_or_exit(txmsg['skip_mapping_checks_warning'],"continue")
|
||||
else:
|
||||
check_mmgen_to_btc_addr_mappings(
|
||||
mmgen_inputs,b2m_map,seed_files,saved_seeds,opts)
|
||||
|
||||
p = "View data for transaction{}? (y)es, (N)o, (v)iew in pager"
|
||||
reply = prompt_and_get_char(p.format(tx_num_str),"YyNnVv",enter_ok=True)
|
||||
if reply and reply in "YyVv":
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,
|
||||
True if reply in "Vv" else False)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,reply in "Vv")
|
||||
|
||||
# Start
|
||||
other_addrs = list(set([i['address'] for i in inputs_data
|
||||
if not parse_mmgen_label(i['account'])[0]]))
|
||||
|
||||
keys = get_keys_from_keylist(from_file['kldata'],other_addrs)
|
||||
|
||||
if other_addrs and not 'use_wallet_dat' in opts:
|
||||
missing_keys_errormsg(other_addrs)
|
||||
sys.exit(2)
|
||||
|
||||
imap = dict([(i['account'].split()[0],i['address']) for i in inputs_data
|
||||
if parse_mmgen_label(i['account'])[0]])
|
||||
omap = dict([(j[0],i) for i,j in b2m_map.items()])
|
||||
sids = set([i[:8] for i in imap.keys()])
|
||||
|
||||
keys += check_maps_from_kafile(imap,"input",from_file['mmdata'],True)
|
||||
check_maps_from_kafile(omap,"output",from_file['mmdata'])
|
||||
|
||||
keys += check_maps_from_seeds(imap,"input",seed_files,saved_seeds,opts,True)
|
||||
check_maps_from_seeds(omap,"output",seed_files,saved_seeds,opts)
|
||||
|
||||
extra_sids = set(saved_seeds.keys()) - sids
|
||||
if extra_sids:
|
||||
msg("Unused seed ID%s: %s" %
|
||||
(suf(extra_sids,"k")," ".join(extra_sids)))
|
||||
|
||||
# Begin signing
|
||||
sig_data = [
|
||||
{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
|
||||
for i in inputs_data]
|
||||
|
||||
if mmgen_inputs and not 'all_keys_from_file' in opts:
|
||||
ml = [i['account'].split()[0] for i in mmgen_inputs]
|
||||
keys += get_keys_for_mmgen_addrs(ml,seed_files,saved_seeds,opts)
|
||||
|
||||
if 'use_wallet_dat' in opts:
|
||||
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys,opts)
|
||||
else:
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
elif other_inputs:
|
||||
if keys:
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
else:
|
||||
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys,opts)
|
||||
if 'use_wallet_dat' in opts:
|
||||
sig_tx = sign_tx_with_bitcoind_wallet(
|
||||
c,tx_hex,tx_num_str,sig_data,keys,opts)
|
||||
else:
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
|
||||
if sig_tx['complete']:
|
||||
msg("OK")
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ import sys
|
|||
import mmgen.config as g
|
||||
from mmgen.Opts import *
|
||||
from mmgen.util import *
|
||||
from mmgen.crypto import get_seed_retry,wallet_to_incog_data
|
||||
from mmgen.crypto import *
|
||||
|
||||
help_data = {
|
||||
'prog_name': g.prog_name,
|
||||
|
|
@ -47,6 +47,7 @@ help_data = {
|
|||
-X, --export-incog-hex Export wallet to incognito hexadecimal format
|
||||
-G, --export-incog-hidden=f,o Hide incognito data in existing file 'f'
|
||||
at offset 'o' (comma-separated)
|
||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
||||
-m, --export-mnemonic Export the wallet's mnemonic to file
|
||||
-s, --export-seed Export the wallet's seed to file
|
||||
""".format(g=g),
|
||||
|
|
@ -60,6 +61,54 @@ to disable this option, then specify '-r0' on the command line.
|
|||
"""
|
||||
}
|
||||
|
||||
def wallet_to_incog_data(infile,opts):
|
||||
|
||||
d = get_data_from_wallet(infile,silent=True)
|
||||
seed_id,key_id,preset,salt,enc_seed = \
|
||||
d[1][0], d[1][1], d[2].split(":")[0], d[3], d[4]
|
||||
|
||||
while True:
|
||||
passwd = get_mmgen_passphrase("{} wallet".format(g.proj_name),opts)
|
||||
key = make_key(passwd, salt, preset, "main key")
|
||||
seed = decrypt_seed(enc_seed, key, seed_id, key_id)
|
||||
if seed: break
|
||||
|
||||
iv = get_random(g.aesctr_iv_len,opts)
|
||||
iv_id = make_iv_chksum(iv)
|
||||
msg("Incog ID: %s" % iv_id)
|
||||
|
||||
if not 'old_incog_fmt' in opts:
|
||||
salt = get_random(g.salt_len,opts)
|
||||
key = make_key(passwd, salt, preset, "incog wallet key")
|
||||
key_id = make_chksum_8(key)
|
||||
from hashlib import sha256
|
||||
chk = sha256(seed).digest()[:8]
|
||||
enc_seed = encrypt_data(chk+seed, key, 1, "seed")
|
||||
|
||||
# IV is used BOTH to initialize counter and to salt password!
|
||||
key = make_key(passwd, iv, preset, "incog wrapper key")
|
||||
m = "incog data"
|
||||
wrap_enc = encrypt_data(salt + enc_seed, key, int(hexlify(iv),16), m)
|
||||
|
||||
return iv+wrap_enc,seed_id,key_id,iv_id,preset
|
||||
|
||||
|
||||
def export_to_hidden_incog(incog_enc,opts):
|
||||
outfile,offset = opts['export_incog_hidden'].split(",") #Already sanity-checked
|
||||
if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
|
||||
|
||||
check_data_fits_file_at_offset(outfile,int(offset),len(incog_enc),"write")
|
||||
|
||||
if not g.quiet: confirm_or_exit("","alter file '%s'" % outfile)
|
||||
import os
|
||||
f = os.open(outfile,os.O_RDWR)
|
||||
os.lseek(f, int(offset), os.SEEK_SET)
|
||||
os.write(f, incog_enc)
|
||||
os.close(f)
|
||||
msg("Data written to file '%s' at offset %s" %
|
||||
(os.path.relpath(outfile),offset))
|
||||
|
||||
|
||||
opts,cmd_args = parse_opts(sys.argv,help_data)
|
||||
|
||||
if 'export_incog_hidden' in opts or 'export_incog_hex' in opts:
|
||||
|
|
@ -82,7 +131,8 @@ elif 'export_incog' in opts:
|
|||
if "export_incog_hidden" in opts:
|
||||
export_to_hidden_incog(incog_enc,opts)
|
||||
else:
|
||||
seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len)*8
|
||||
z = 0 if 'old_incog_fmt' in opts else 8
|
||||
seed_len = (len(incog_enc)-g.salt_len-g.aesctr_iv_len-z)*8
|
||||
fn = "%s-%s-%s[%s,%s].%s" % (
|
||||
seed_id, key_id, iv_id, seed_len, preset,
|
||||
g.incog_hex_ext if "export_incog_hex" in opts else g.incog_ext
|
||||
|
|
@ -102,8 +152,7 @@ else:
|
|||
if 'export_mnemonic' in opts:
|
||||
wl = get_default_wordlist()
|
||||
from mmgen.mnemonic import get_mnemonic_from_seed
|
||||
p = True if g.debug else False
|
||||
mn = get_mnemonic_from_seed(seed, wl, g.default_wl, print_info=p)
|
||||
mn = get_mnemonic_from_seed(seed, wl, g.default_wl, g.debug)
|
||||
fn = "%s.%s" % (make_chksum_8(seed).upper(), g.mn_ext)
|
||||
write_to_file_or_stdout(fn, " ".join(mn)+"\n", opts, "mnemonic data")
|
||||
|
||||
|
|
|
|||
|
|
@ -55,6 +55,9 @@ help_data = {
|
|||
i.e. a "brainwallet", using seed length 'l' and
|
||||
hash preset 'p' (comma-separated)
|
||||
-g, --from-incog Generate wallet from an incognito-format wallet
|
||||
-G, --from-incog-hidden= f,o,l Generate keys from incognito data in file
|
||||
'f' at offset 'o', with seed length of 'l'
|
||||
-o, --old-incog-fmt Use old (pre-0.7.8) incog format
|
||||
-m, --from-mnemonic Generate wallet from an Electrum-like mnemonic
|
||||
-s, --from-seed Generate wallet from a seed in .{g.seed_ext} format
|
||||
""".format(seed_lens=",".join([str(i) for i in g.seed_lens]), g=g),
|
||||
|
|
@ -94,6 +97,30 @@ in all future invocations with that passphrase.
|
|||
""".format(g=g)
|
||||
}
|
||||
|
||||
wmsg = {
|
||||
'choose_wallet_passphrase': """
|
||||
You must choose a passphrase to encrypt the wallet 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(),
|
||||
'brain_warning': """
|
||||
############################## EXPERTS ONLY! ##############################
|
||||
|
||||
A brainwallet will be secure only if you really know what you're doing and
|
||||
have put much care into its creation. {} assumes no responsibility for
|
||||
coins stolen as a result of a poorly crafted brainwallet passphrase.
|
||||
|
||||
A key will be generated from your passphrase using the parameters requested
|
||||
by you: seed length {}, hash preset '{}'. For brainwallets it's highly
|
||||
recommended to use one of the higher-numbered presets
|
||||
|
||||
Remember the seed length and hash preset parameters you've specified. To
|
||||
generate the correct keys/addresses associated with this passphrase in the
|
||||
future, you must continue using these same parameters
|
||||
""",
|
||||
}
|
||||
|
||||
opts,cmd_args = parse_opts(sys.argv,help_data)
|
||||
|
||||
if 'show_hash_presets' in opts: show_hash_presets()
|
||||
|
|
@ -121,16 +148,17 @@ else: usage(help_data)
|
|||
do_license_msg()
|
||||
|
||||
if 'from_brain' in opts and not g.quiet:
|
||||
confirm_or_exit(cmessages['brain_warning'].format(
|
||||
confirm_or_exit(wmsg['brain_warning'].format(
|
||||
g.proj_name, *get_from_brain_opt_params(opts)),
|
||||
"continue")
|
||||
|
||||
for i in 'from_mnemonic','from_brain','from_seed','from_incog':
|
||||
if infile or (i in opts):
|
||||
seed = get_seed_retry(infile,opts)
|
||||
if "from_incog" in opts or get_extension(infile) == g.incog_ext:
|
||||
qmsg(cmessages['incog'] % make_chksum_8(seed))
|
||||
else: qmsg("")
|
||||
# if "from_incog" in opts or get_extension(infile) == g.incog_ext:
|
||||
# qmsg(cmessages['incog'] % make_chksum_8(seed))
|
||||
# else: qmsg("")
|
||||
qmsg("")
|
||||
break
|
||||
else:
|
||||
# Truncate random data for smaller seed lengths
|
||||
|
|
@ -138,7 +166,7 @@ else:
|
|||
|
||||
salt = sha256(get_random(128,opts)).digest()[:g.salt_len]
|
||||
|
||||
qmsg(cmessages['choose_wallet_passphrase'] % opts['hash_preset'])
|
||||
qmsg(wmsg['choose_wallet_passphrase'] % opts['hash_preset'])
|
||||
|
||||
passwd = get_new_passphrase("new {} wallet".format(g.proj_name), opts)
|
||||
|
||||
|
|
|
|||
|
|
@ -17,9 +17,13 @@
|
|||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
"""
|
||||
mnemonic.py: Mnemomic routines for the MMGen suite
|
||||
mnemonic.py: Mnemonic routines for the MMGen suite
|
||||
"""
|
||||
|
||||
import sys
|
||||
from mmgen.util import msg,msg_r,make_chksum_8
|
||||
import mmgen.config as g
|
||||
|
||||
wl_checksums = {
|
||||
"electrum": '5ca31424',
|
||||
"tirosh": '1a5faeff'
|
||||
|
|
@ -27,70 +31,77 @@ wl_checksums = {
|
|||
|
||||
# These are the only base-1626 specific configs:
|
||||
mn_base = 1626
|
||||
def mn_fill(mn): return len(mn) * 8 / 3
|
||||
def mn_len(hexnum): return len(hexnum) * 3 / 8
|
||||
def mn2hex_pad(mn): return len(mn) * 8 / 3
|
||||
def hex2mn_pad(hexnum): return len(hexnum) * 3 / 8
|
||||
|
||||
import sys
|
||||
|
||||
from mmgen.util import msg,make_chksum_8
|
||||
import mmgen.config as g
|
||||
|
||||
# These universal base-conversion routines work for any base
|
||||
|
||||
def baseNtohex(base,words,wordlist,fill=0):
|
||||
# Universal base-conversion routines:
|
||||
def baseNtohex(base,words,wordlist,pad=0):
|
||||
deconv = \
|
||||
[wordlist.index(words[::-1][i])*(base**i) for i in range(len(words))]
|
||||
return hex(sum(deconv))[2:].rstrip('L').zfill(fill)
|
||||
return ("{:0%sx}"%pad).format(sum(deconv))
|
||||
|
||||
def hextobaseN(base,hexnum,wordlist,mn_len):
|
||||
num = int(hexnum,16)
|
||||
return [wordlist[num / (base**i) % base] for i in range(mn_len)][::-1]
|
||||
def hextobaseN(base,hexnum,wordlist,pad=0):
|
||||
num,ret = int(hexnum,16),[]
|
||||
while num:
|
||||
ret.append(num % base)
|
||||
num /= base
|
||||
return [wordlist[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
|
||||
|
||||
def get_seed_from_mnemonic(mn,wl):
|
||||
def get_seed_from_mnemonic(mn,wl,silent=False,label=""):
|
||||
|
||||
if len(mn) not in g.mnemonic_lens:
|
||||
msg("Bad mnemonic (%i words). Allowed numbers of words: %s" %
|
||||
(len(mn),", ".join([str(i) for i in g.mnemonic_lens])))
|
||||
return False
|
||||
if len(mn) not in g.mn_lens:
|
||||
msg("Invalid mnemonic (%i words). Allowed numbers of words: %s" %
|
||||
(len(mn),", ".join([str(i) for i in g.mn_lens])))
|
||||
sys.exit(3)
|
||||
|
||||
for n,w in enumerate(mn,1):
|
||||
if w not in wl:
|
||||
msg("Bad mnemonic: word number %s is not in the wordlist" % n)
|
||||
return False
|
||||
msg("Invalid mnemonic: word #%s is not in the wordlist" % n)
|
||||
sys.exit(3)
|
||||
|
||||
from binascii import unhexlify
|
||||
seed = unhexlify(baseNtohex(mn_base,mn,wl,mn_fill(mn)))
|
||||
msg("Valid mnemomic for seed ID %s" % make_chksum_8(seed))
|
||||
hseed = baseNtohex(mn_base,mn,wl,mn2hex_pad(mn))
|
||||
|
||||
return seed
|
||||
rev = hextobaseN(mn_base,hseed,wl,hex2mn_pad(hseed))
|
||||
if rev != mn:
|
||||
msg("ERROR: mnemonic recomputed from seed not the same as original")
|
||||
msg("Recomputed mnemonic:\n%s" % " ".join(rev))
|
||||
sys.exit(3)
|
||||
|
||||
if not silent:
|
||||
msg("Valid mnemonic for seed ID %s" % make_chksum_8(unhexlify(hseed)))
|
||||
|
||||
return unhexlify(hseed)
|
||||
|
||||
|
||||
def get_mnemonic_from_seed(seed, wl, label, print_info=False):
|
||||
def get_mnemonic_from_seed(seed, wl, label="", verbose=False):
|
||||
|
||||
if len(seed)*8 not in g.seed_lens:
|
||||
msg("%s: invalid seed length" % len(seed))
|
||||
sys.exit(3)
|
||||
|
||||
from binascii import hexlify
|
||||
|
||||
if print_info:
|
||||
msg("Wordlist: %s" % label.capitalize())
|
||||
msg("Seed length: %s bits" % (len(seed) * 8))
|
||||
msg("Seed: %s" % hexlify(seed))
|
||||
|
||||
hseed = hexlify(seed)
|
||||
mn = hextobaseN(mn_base,hseed,wl,mn_len(hseed))
|
||||
mn = hextobaseN(mn_base,hseed,wl,hex2mn_pad(hseed))
|
||||
|
||||
if print_info:
|
||||
if verbose:
|
||||
msg("Wordlist: %s" % label.capitalize())
|
||||
msg("Seed length: %s bits" % (len(seed) * 8))
|
||||
msg("Seed: %s" % hseed)
|
||||
msg("mnemonic (%s words):\n%s" % (len(mn), " ".join(mn)))
|
||||
|
||||
if int(baseNtohex(mn_base,mn,wl,mn_fill(mn)),16) != int(hexlify(seed),16):
|
||||
rev = baseNtohex(mn_base,mn,wl,mn2hex_pad(mn))
|
||||
if rev != hseed:
|
||||
msg("ERROR: seed recomputed from wordlist not the same as original seed!")
|
||||
msg("Recomputed seed %s" % baseNtohex(mn_base,mn,wl,mn_fill(mn)))
|
||||
msg("Original seed: %s" % hseed)
|
||||
msg("Recomputed seed: %s" % rev)
|
||||
sys.exit(3)
|
||||
|
||||
return mn
|
||||
|
||||
|
||||
def check_wordlist(wl_str,label):
|
||||
|
||||
wl = wl_str.strip().split("\n")
|
||||
def check_wordlist(wl,label):
|
||||
|
||||
print "Wordlist: %s" % label.capitalize()
|
||||
|
||||
|
|
|
|||
|
|
@ -22,26 +22,14 @@ bitcoin.py: Test suite for mmgen.bitcoin module
|
|||
import mmgen.bitcoin as b
|
||||
from mmgen.util import msg
|
||||
from mmgen.tests.test import *
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
from binascii import hexlify
|
||||
import sys
|
||||
|
||||
def b58_randenc():
|
||||
r = get_random(24)
|
||||
r_enc = b.b58encode(r)
|
||||
print "Data (hex): %s" % hexlify(r)
|
||||
print "Base 58: %s" % r_enc
|
||||
r_dec = b.b58decode(r_enc)
|
||||
print "Decoded data: %s" % hexlify(r_dec)
|
||||
if r_dec != r:
|
||||
print "ERROR! Decoded data doesn't match original"
|
||||
sys.exit(9)
|
||||
|
||||
def keyconv_compare_randloop(loops, quiet=False):
|
||||
for i in range(1,int(loops)+1):
|
||||
|
||||
|
||||
wif = numtowif_rand(quiet=True)
|
||||
wif = _numtowif_rand(quiet=True)
|
||||
|
||||
if not quiet: sys.stderr.write("-- %s --\n" % i)
|
||||
ret = keyconv_compare(wif,quiet)
|
||||
|
|
@ -55,7 +43,6 @@ def keyconv_compare_randloop(loops, quiet=False):
|
|||
else:
|
||||
print "%s iterations completed" % i
|
||||
|
||||
|
||||
def keyconv_compare(wif,quiet=False):
|
||||
do_msg = nomsg if quiet else msg
|
||||
do_msg("WIF: %s" % wif)
|
||||
|
|
@ -81,135 +68,24 @@ def keyconv_compare(wif,quiet=False):
|
|||
def _do_hextowif(hex_in,quiet=False):
|
||||
do_msg = nomsg if quiet else msg
|
||||
do_msg("Input: %s" % hex_in)
|
||||
wif = numtowif(int(hex_in,16))
|
||||
wif = b.numtowif(int(hex_in,16))
|
||||
do_msg("WIF encoded: %s" % wif)
|
||||
wif_dec = wiftohex(wif)
|
||||
wif_dec = b.wiftohex(wif)
|
||||
do_msg("WIF decoded: %s" % wif_dec)
|
||||
if hex_in != wif_dec:
|
||||
print "ERROR! Decoded data doesn't match original data"
|
||||
sys.exit(9)
|
||||
return wif
|
||||
|
||||
|
||||
def hextowiftopubkey(hex_in,quiet=False):
|
||||
if len(hex_in) != 64:
|
||||
print "Input must be a hex number 64 bits in length (%s input)" \
|
||||
% len(hex_in)
|
||||
sys.exit(2)
|
||||
|
||||
wif = _do_hextowif(hex_in,quiet=quiet)
|
||||
|
||||
keyconv_compare(wif)
|
||||
|
||||
|
||||
def numtowif_rand(quiet=False):
|
||||
def _numtowif_rand(quiet=False):
|
||||
r_hex = hexlify(get_random(32))
|
||||
|
||||
return _do_hextowif(r_hex,quiet)
|
||||
|
||||
|
||||
def strtob58(s,quiet=False):
|
||||
print "Input: %s" % s
|
||||
s_enc = b.b58encode(s)
|
||||
print "Encoded data: %s" % s_enc
|
||||
s_dec = b.b58decode(s_enc)
|
||||
print "Decoded data: %s" % s_dec
|
||||
test_equality(s,s_dec,[""],quiet)
|
||||
|
||||
def hextob58(s_in,f_enc=b.b58encode, f_dec=b.b58decode, quiet=False):
|
||||
do_msg = nomsg if quiet else msg
|
||||
do_msg("Input: %s" % s_in)
|
||||
s_bin = unhexlify(s_in)
|
||||
s_enc = f_enc(s_bin)
|
||||
do_msg("Encoded data: %s" % s_enc)
|
||||
s_dec = hexlify(f_dec(s_enc))
|
||||
do_msg("Recoded data: %s" % s_dec)
|
||||
test_equality(s_in,s_dec,["0"],quiet)
|
||||
|
||||
def b58tohex(s_in,f_dec=b.b58decode, f_enc=b.b58encode,quiet=False):
|
||||
print "Input: %s" % s_in
|
||||
s_dec = f_dec(s_in)
|
||||
print "Decoded data: %s" % hexlify(s_dec)
|
||||
s_enc = f_enc(s_dec)
|
||||
print "Recoded data: %s" % s_enc
|
||||
test_equality(s_in,s_enc,["1"],quiet)
|
||||
|
||||
def hextob58_pad(s_in, quiet=False):
|
||||
hextob58(s_in,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet)
|
||||
|
||||
def b58tohex_pad(s_in, quiet=False):
|
||||
b58tohex(s_in,f_dec=b.b58decode_pad, f_enc=b.b58encode_pad, quiet=quiet)
|
||||
|
||||
def hextob58_pad_randloop(loops, quiet=False):
|
||||
for i in range(1,int(loops)+1):
|
||||
r = hexlify(get_random(32))
|
||||
hextob58(r,f_enc=b.b58encode_pad, f_dec=b.b58decode_pad, quiet=quiet)
|
||||
if not quiet: print
|
||||
if not i % 100 and quiet:
|
||||
sys.stderr.write("\riteration: %i " % i)
|
||||
|
||||
sys.stderr.write("\r%s iterations completed\n" % i)
|
||||
|
||||
def test_wiftohex(s_in,f_dec=b.wiftohex,f_enc=b.numtowif):
|
||||
print "Input: %s" % s_in
|
||||
s_dec = f_dec(s_in)
|
||||
print "Decoded data: %s" % s_dec
|
||||
s_enc = f_enc(int(s_dec,16))
|
||||
print "Recoded data: %s" % s_enc
|
||||
|
||||
def hextosha256(s_in):
|
||||
print "Entered data: %s" % s_in
|
||||
s_enc = sha256(unhexlify(s_in)).hexdigest()
|
||||
print "Encoded data: %s" % s_enc
|
||||
|
||||
def pubhextoaddr(s_in):
|
||||
print "Entered data: %s" % s_in
|
||||
s_enc = b.pubhex2addr(s_in)
|
||||
print "Encoded data: %s" % s_enc
|
||||
|
||||
def hextowif_comp(s_in):
|
||||
print "Entered data: %s" % s_in
|
||||
s_enc = b.hextowif(s_in,compressed=True)
|
||||
print "Encoded data: %s" % s_enc
|
||||
s_dec = b.wiftohex(s_enc,compressed=True)
|
||||
print "Decoded data: %s" % s_dec
|
||||
|
||||
def wiftohex_comp(s_in):
|
||||
print "Entered data: %s" % s_in
|
||||
s_enc = b.wiftohex(s_in,compressed=True)
|
||||
print "Encoded data: %s" % s_enc
|
||||
s_dec = b.hextowif(s_enc,compressed=True)
|
||||
print "Decoded data: %s" % s_dec
|
||||
|
||||
def privhextoaddr_comp(hexpriv):
|
||||
print b.privnum2addr(int(hexpriv,16),compressed=True)
|
||||
|
||||
def wiftoaddr_comp(s_in):
|
||||
print "Entered data: %s" % s_in
|
||||
s_enc = b.wiftohex(s_in,compressed=True)
|
||||
print "Encoded data: %s" % s_enc
|
||||
s_enc = b.privnum2addr(int(s_enc,16),compressed=True)
|
||||
print "Encoded data: %s" % s_enc
|
||||
|
||||
tests = {
|
||||
"keyconv_compare": ['wif [str]','quiet [bool=False]'],
|
||||
"keyconv_compare_randloop": ['iterations [int]','quiet [bool=False]'],
|
||||
"b58_randenc": ['quiet [bool=False]'],
|
||||
"strtob58": ['string [str]','quiet [bool=False]'],
|
||||
"hextob58": ['hexnum [str]','quiet [bool=False]'],
|
||||
"b58tohex": ['b58num [str]','quiet [bool=False]'],
|
||||
"hextob58_pad": ['hexnum [str]','quiet [bool=False]'],
|
||||
"b58tohex_pad": ['b58num [str]','quiet [bool=False]'],
|
||||
"hextob58_pad_randloop": ['iterations [int]','quiet [bool=False]'],
|
||||
"test_wiftohex": ['wif [str]', 'quiet [bool=False]'],
|
||||
"numtowif_rand": ['quiet [bool=False]'],
|
||||
"hextosha256": ['hexnum [str]','quiet [bool=False]'],
|
||||
"hextowiftopubkey": ['hexnum [str]','quiet [bool=False]'],
|
||||
"pubhextoaddr": ['hexnum [str]','quiet [bool=False]'],
|
||||
"hextowif_comp": ['hexnum [str]'],
|
||||
"wiftohex_comp": ['wif [str]'],
|
||||
"privhextoaddr_comp": ['hexnum [str]'],
|
||||
"wiftoaddr_comp": ['wif [str]'],
|
||||
}
|
||||
|
||||
args = process_test_args(sys.argv, tests)
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ def Vmsg_r(s):
|
|||
opts = {}
|
||||
commands = {
|
||||
"strtob58": ['<string> [str]'],
|
||||
"b58tostr": ['<b58 number> [str]'],
|
||||
"hextob58": ['<hex number> [str]'],
|
||||
"b58tohex": ['<b58 number> [str]'],
|
||||
"b58tostr": ['<b58 number> [str]'],
|
||||
"b58randenc": [],
|
||||
"randhex": ['nbytes [int=32]'],
|
||||
"randwif": ['compressed [bool=False]'],
|
||||
|
|
@ -51,6 +51,8 @@ commands = {
|
|||
"hex2wif": ['<private key in hex format> [str]', 'compressed [bool=False]'],
|
||||
"hexdump": ['<infile> [str]', 'cols [int=8]', 'line_nums [bool=True]'],
|
||||
"unhexdump": ['<infile> [str]'],
|
||||
"hex2mn": ['<hexadecimal string> [str]','wordlist [str="electrum"]'],
|
||||
"mn2hex": ['<mnemonic> [str]', 'wordlist [str="electrum"]'],
|
||||
"mn_rand128": ['wordlist [str="electrum"]'],
|
||||
"mn_rand192": ['wordlist [str="electrum"]'],
|
||||
"mn_rand256": ['wordlist [str="electrum"]'],
|
||||
|
|
@ -59,10 +61,11 @@ commands = {
|
|||
"id8": ['<infile> [str]'],
|
||||
"id6": ['<infile> [str]'],
|
||||
"str2id6": ['<string (spaces are ignored)> [str]'],
|
||||
"listaddresses":['minconf [int=1]', 'showempty [bool=False]'],
|
||||
"listaddresses":['minconf [int=1]','showempty [bool=False]','pager [bool=False]'],
|
||||
"getbalance": ['minconf [int=1]'],
|
||||
"viewtx": ['<MMGen tx file> [str]','pager [bool=False]'],
|
||||
"check_addrfile": ['<MMGen addr file> [str]'],
|
||||
"addrfile_chksum": ['<MMGen addr file> [str]'],
|
||||
"keyaddrfile_chksum": ['<MMGen addr file> [str]'],
|
||||
"find_incog_data": ['<file or device name> [str]','<Incog ID> [str]','keep_searching [bool=False]'],
|
||||
"hexreverse": ['<hexadecimal string> [str]'],
|
||||
"sha256x2": ['<str, hexstr or filename> [str]',
|
||||
|
|
@ -122,8 +125,9 @@ command_help = """
|
|||
* The encrypted file is indistinguishable from random data
|
||||
|
||||
{pnm}-specific operations:
|
||||
check_addrfile - compute checksum and address list for {pnm} address file
|
||||
find_incog_data - Use an Incog ID to find hidden incognito wallet data
|
||||
addrfile_chksum - compute checksum for {pnm} address file
|
||||
keyaddrfile_chksum - compute checksum for {pnm} key 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)
|
||||
str2id6 - generate 6-character {pnm} ID for a string, ignoring spaces
|
||||
|
|
@ -135,6 +139,8 @@ command_help = """
|
|||
mn_rand256 - generate random 256-bit mnemonic
|
||||
mn_stats - show stats for mnemonic wordlist
|
||||
mn_printlist - print mnemonic wordlist
|
||||
hex2mn - convert a 16, 24 or 32-byte number in hex format to a mnemonic
|
||||
mn2hex - convert a 12, 18 or 24-word mnemonic to a number in hex format
|
||||
|
||||
IMPORTANT NOTE: Though {pnm} mnemonics use the Electrum wordlist, they're
|
||||
computed using a different algorithm and are NOT Electrum-compatible!
|
||||
|
|
@ -286,37 +292,45 @@ def get_wordlist(wordlist):
|
|||
Msg('"%s": invalid wordlist. Valid choices: %s' %
|
||||
(wordlist,'"'+'" "'.join(wordlists)+'"'))
|
||||
sys.exit(1)
|
||||
return el if wordlist == "electrum" else tl
|
||||
return (el if wordlist == "electrum" else tl).strip().split("\n")
|
||||
|
||||
def do_random_mn(nbytes,wordlist):
|
||||
r = get_random(nbytes,opts)
|
||||
wlists = wordlists if wordlist == "all" else [wordlist]
|
||||
for wl in wlists:
|
||||
l = get_wordlist(wl)
|
||||
if wl == wlists[0]: Vmsg("Seed: %s" % ba.hexlify(r))
|
||||
mn = get_mnemonic_from_seed(r,l.strip().split("\n"),
|
||||
wordlist,print_info=False)
|
||||
Vmsg("%s wordlist mnemonic:" % (wl.capitalize()))
|
||||
Vmsg("Seed: %s" % ba.hexlify(r))
|
||||
for wlname in (wordlists if wordlist == "all" else [wordlist]):
|
||||
wl = get_wordlist(wlname)
|
||||
mn = get_mnemonic_from_seed(r,wl,wordlist)
|
||||
Vmsg("%s wordlist mnemonic:" % (wlname.capitalize()))
|
||||
print " ".join(mn)
|
||||
|
||||
def mn_rand128(wordlist="electrum"): do_random_mn(16,wordlist)
|
||||
def mn_rand192(wordlist="electrum"): do_random_mn(24,wordlist)
|
||||
def mn_rand256(wordlist="electrum"): do_random_mn(32,wordlist)
|
||||
|
||||
def hex2mn(s,wordlist="electrum"):
|
||||
import mmgen.mnemonic
|
||||
wl = get_wordlist(wordlist)
|
||||
print " ".join(get_mnemonic_from_seed(ba.unhexlify(s), wl, wordlist))
|
||||
|
||||
def mn2hex(s,wordlist="electrum"):
|
||||
import mmgen.mnemonic
|
||||
wl = get_wordlist(wordlist)
|
||||
print ba.hexlify(get_seed_from_mnemonic(s.split(),wl,True))
|
||||
|
||||
def mn_stats(wordlist="electrum"):
|
||||
l = get_wordlist(wordlist)
|
||||
check_wordlist(l,wordlist)
|
||||
|
||||
def mn_printlist(wordlist="electrum"):
|
||||
l = get_wordlist(wordlist)
|
||||
print "%s" % l.strip()
|
||||
wl = get_wordlist(wordlist)
|
||||
print "\n".join(wl)
|
||||
|
||||
def id8(infile): print make_chksum_8(get_data_from_file(infile,dash=True))
|
||||
def id6(infile): print make_chksum_6(get_data_from_file(infile,dash=True))
|
||||
def str2id6(s): print make_chksum_6("".join(s.split()))
|
||||
|
||||
# List MMGen addresses and their balances:
|
||||
def listaddresses(minconf=1,showempty=False):
|
||||
def listaddresses(minconf=1,showempty=False,pager=False):
|
||||
from mmgen.tx import connect_to_bitcoind,trim_exponent,is_mmgen_addr
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
|
|
@ -349,7 +363,7 @@ def listaddresses(minconf=1,showempty=False):
|
|||
max([len(k) for k in addrs.keys()]),
|
||||
max([len(str(addrs[k][1])) for k in addrs.keys()])
|
||||
)
|
||||
print fs % ("ADDRESS","COMMENT","BALANCE")
|
||||
out = [ fs % ("ADDRESS","COMMENT","BALANCE") ]
|
||||
|
||||
def s_mmgen(ma):
|
||||
return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *ma.split("_"))
|
||||
|
|
@ -357,9 +371,13 @@ def listaddresses(minconf=1,showempty=False):
|
|||
old_sid = ""
|
||||
for k in sorted(addrs.keys(),key=s_mmgen):
|
||||
sid,num = k.split("_")
|
||||
if old_sid and old_sid != sid: print
|
||||
if old_sid and old_sid != sid: out.append("")
|
||||
old_sid = sid
|
||||
print fs % (sid+":"+num, addrs[k][1], trim_exponent(addrs[k][0]))
|
||||
out.append(fs % (sid+":"+num, addrs[k][1], trim_exponent(addrs[k][0])))
|
||||
|
||||
o = "\n".join(out)
|
||||
if pager: do_pager(o)
|
||||
else: print o
|
||||
|
||||
|
||||
def getbalance(minconf=1):
|
||||
|
|
@ -390,10 +408,11 @@ def viewtx(infile,pager=False):
|
|||
c = connect_to_bitcoind()
|
||||
tx_data = get_lines_from_file(infile,"transaction data")
|
||||
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_data(tx_data,infile)
|
||||
metadata,tx_hex,inputs_data,b2m_map,comment = parse_tx_file(tx_data,infile)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager)
|
||||
|
||||
def check_addrfile(infile): parse_addrs_file(infile)
|
||||
def addrfile_chksum(infile): parse_addrfile(infile,{})
|
||||
def keyaddrfile_chksum(infile): parse_keyaddr_file(infile,{})
|
||||
|
||||
def hexreverse(hex_str):
|
||||
print ba.hexlify(decode_pretty_hexdump(hex_str)[::-1])
|
||||
|
|
@ -418,7 +437,7 @@ def pubkey2hexaddr(pubkeyhex):
|
|||
print bitcoin.pubhex2hexaddr(pubkeyhex)
|
||||
|
||||
def pubkey2addr(pubkeyhex):
|
||||
print bitcoin.pubhex2addr(pubkeyhex)
|
||||
print bitcoin.hexaddr2addr(bitcoin.pubhex2hexaddr(pubkeyhex))
|
||||
|
||||
def privhex2addr(privkeyhex,compressed=False):
|
||||
print bitcoin.privnum2addr(int(privkeyhex,16),compressed)
|
||||
|
|
@ -444,7 +463,7 @@ def encrypt(infile,outfile="",hash_preset=''):
|
|||
def decrypt(infile,outfile="",hash_preset=''):
|
||||
enc_d = get_data_from_file(infile,"encrypted data")
|
||||
while True:
|
||||
dec_d = mmgen_decrypt(enc_d,"user data","",opts)
|
||||
dec_d = mmgen_decrypt(enc_d,"user data")
|
||||
if dec_d: break
|
||||
msg("Trying again...")
|
||||
if outfile == '-':
|
||||
|
|
@ -463,6 +482,10 @@ def find_incog_data(filename,iv_id,keep_searching=False):
|
|||
ivsize,bsize,mod = g.aesctr_iv_len,4096,4096*8
|
||||
n,carry = 0," "*ivsize
|
||||
f = os.open(filename,os.O_RDONLY)
|
||||
for ch in iv_id:
|
||||
if ch not in "0123456789ABCDEF":
|
||||
msg("'%s': invalid Incog ID" % iv_id)
|
||||
sys.exit(2)
|
||||
while True:
|
||||
d = os.read(f,bsize)
|
||||
if not d: break
|
||||
|
|
|
|||
832
mmgen/tx.py
832
mmgen/tx.py
|
|
@ -26,82 +26,7 @@ from decimal import Decimal
|
|||
|
||||
import mmgen.config as g
|
||||
from mmgen.util import *
|
||||
from mmgen.crypto import get_seed_retry
|
||||
from mmgen.term import do_pager,get_char
|
||||
|
||||
txmsg = {
|
||||
'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
|
||||
'throwaway_change': """
|
||||
ERROR: This transaction produces change (%s BTC); however, no change address
|
||||
was specified.
|
||||
""".strip(),
|
||||
'mixed_inputs': """
|
||||
NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs, which
|
||||
makes the signing process more complicated. When signing the transaction, keys
|
||||
for the non-mmgen inputs must be supplied in a separate file using either the
|
||||
'-k' or '-K' option to '{}-txsign'.
|
||||
|
||||
Selected mmgen inputs: %s""".format(g.proj_name.lower()),
|
||||
'too_many_acct_addresses': """
|
||||
ERROR: More than one address found for account: "%s".
|
||||
The tracking "wallet.dat" file appears to have been altered by a non-{g.proj_name}
|
||||
program. Please restore "wallet.dat" from a backup or create a new wallet
|
||||
and re-import your addresses.""".strip().format(g=g),
|
||||
'addrfile_no_data_msg': """
|
||||
No data found for MMgen address '%s'. Please import this address into
|
||||
your tracking wallet, or supply an address file for it on the command line.
|
||||
""".strip(),
|
||||
'addrfile_warn_msg': """
|
||||
Warning: output address '{mmaddr}' is not in the tracking wallet, which means
|
||||
its balance will not be tracked. You're strongly advised to import the address
|
||||
into your tracking wallet before broadcasting this transaction.
|
||||
""".strip(),
|
||||
'addrfile_fail_msg': """
|
||||
No data for MMgen address '{mmaddr}' could be found in either the tracking
|
||||
wallet or the supplied address file. Please import this address into your
|
||||
tracking wallet, or supply an address file for it on the command line.
|
||||
""".strip(),
|
||||
'no_spendable_outputs': """
|
||||
No spendable outputs found! Import addresses with balances into your
|
||||
watch-only wallet using '{}-addrimport' and then re-run this program.
|
||||
""".strip().format(g.proj_name.lower()),
|
||||
'mapping_error': """
|
||||
MMGen -> BTC address mappings differ!
|
||||
In transaction: %s
|
||||
Generated from seed: %s
|
||||
""".strip(),
|
||||
'skip_mapping_checks_warning': """
|
||||
You've chosen the '--all-keys-from-file' option. Since all signing keys will
|
||||
be taken from this file, no {pnm} seed source will be consulted and {pnm}-to-
|
||||
BTC mapping checks cannot not be performed. Were an attacker to compromise
|
||||
your tracking wallet or raw transaction file, he could thus cause you to spend
|
||||
coin to an unintended address. For greater security, supply a trusted {pnm}
|
||||
address file for your output addresses on the command line.
|
||||
""".strip().format(pnm=g.proj_name),
|
||||
'missing_mappings': """
|
||||
No information was found in the supplied address files for the following {pnm}
|
||||
addresses: %s
|
||||
The {pnm}-to-BTC mappings for these addresses cannot be verified!
|
||||
""".strip().format(pnm=g.proj_name),
|
||||
}
|
||||
|
||||
|
||||
def connect_to_bitcoind():
|
||||
|
||||
host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
|
||||
cfg = get_bitcoind_cfg_options((user,passwd))
|
||||
|
||||
import mmgen.rpc.connection
|
||||
f = mmgen.rpc.connection.BitcoinConnection
|
||||
|
||||
try:
|
||||
c = f(cfg[user],cfg[passwd],host,port)
|
||||
except:
|
||||
msg("Unable to establish RPC connection with bitcoind")
|
||||
sys.exit(2)
|
||||
|
||||
return c
|
||||
|
||||
from mmgen.term import do_pager
|
||||
|
||||
def trim_exponent(n):
|
||||
'''Remove exponent and trailing zeros.
|
||||
|
|
@ -109,8 +34,6 @@ def trim_exponent(n):
|
|||
d = Decimal(n)
|
||||
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
|
||||
|
||||
|
||||
|
||||
def is_btc_amt(amt):
|
||||
# amt must be a string!
|
||||
|
||||
|
|
@ -134,197 +57,81 @@ def is_btc_amt(amt):
|
|||
|
||||
return trim_exponent(ret)
|
||||
|
||||
|
||||
def normalize_btc_amt(amt):
|
||||
# amt must be a string!
|
||||
ret = is_btc_amt(amt)
|
||||
if ret: return ret
|
||||
else: sys.exit(3)
|
||||
|
||||
|
||||
def get_bitcoind_cfg_options(cfg_keys):
|
||||
|
||||
if "HOME" in os.environ: # Linux
|
||||
homedir,datadir = os.environ["HOME"],".bitcoin"
|
||||
elif "HOMEPATH" in os.environ: # Windows:
|
||||
homedir,data_dir = os.environ["HOMEPATH"],r"Application Data\Bitcoin"
|
||||
else:
|
||||
msg("Neither $HOME nor %HOMEPATH% are set")
|
||||
msg("Don't know where to look for 'bitcoin.conf'")
|
||||
sys.exit(3)
|
||||
|
||||
cfg_file = os.sep.join((homedir, datadir, "bitcoin.conf"))
|
||||
|
||||
cfg = dict([(k,v) for k,v in [split2(line.translate(None,"\t "),"=")
|
||||
for line in get_lines_from_file(cfg_file)] if k in cfg_keys])
|
||||
|
||||
for k in set(cfg_keys) - set(cfg.keys()):
|
||||
msg("Configuration option '%s' must be set in %s" % (k,cfg_file))
|
||||
sys.exit(2)
|
||||
|
||||
return cfg
|
||||
|
||||
|
||||
def format_unspent_outputs_for_printing(out,sort_info,total):
|
||||
|
||||
pfs = " %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s"
|
||||
pout = [pfs % ("Num","TX id,Vout","Address","MMgen ID",
|
||||
"Amount (BTC)","Conf.","Age (days)", "Comment")]
|
||||
|
||||
for n,i in enumerate(out):
|
||||
addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address
|
||||
tx = " " * 63 + "=" \
|
||||
if i.skip == "txid" and "grouped" in sort_info else str(i.txid)
|
||||
|
||||
s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,
|
||||
i.mmid,i.amt,i.confirmations,i.days,i.label)
|
||||
pout.append(s.rstrip())
|
||||
|
||||
return \
|
||||
"Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format(
|
||||
make_timestr(), " ".join(sort_info), "\n".join(pout), total
|
||||
)
|
||||
|
||||
|
||||
def sort_and_view(unspent,opts):
|
||||
|
||||
def s_amt(i): return i.amount
|
||||
def s_txid(i): return "%s %03s" % (i.txid,i.vout)
|
||||
def s_addr(i): return i.address
|
||||
def s_age(i): return i.confirmations
|
||||
def s_mmgen(i):
|
||||
m = parse_mmgen_label(i.account)[0]
|
||||
if m: return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *m.split(":"))
|
||||
else: return "G" + i.account
|
||||
|
||||
sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True
|
||||
unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default
|
||||
|
||||
total = trim_exponent(sum([i.amount for i in unspent]))
|
||||
max_acct_len = max([len(i.account) for i in unspent])
|
||||
|
||||
hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s"
|
||||
options_msg = """
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
|
||||
Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
|
||||
""".strip()
|
||||
prompt = \
|
||||
"('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
|
||||
|
||||
from copy import deepcopy
|
||||
from mmgen.term import get_terminal_size
|
||||
|
||||
write_to_file_msg = ""
|
||||
msg("")
|
||||
|
||||
while True:
|
||||
cols = get_terminal_size()[0]
|
||||
if cols < g.min_screen_width:
|
||||
msg("%s-txcreate requires a screen at least %s characters wide" %
|
||||
(g.proj_name.lower(),g.min_screen_width))
|
||||
sys.exit(2)
|
||||
|
||||
addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
|
||||
acct_w = min(max_acct_len, max(24,int(addr_w-10)))
|
||||
btaddr_w = addr_w - acct_w - 1
|
||||
tx_w = max(11,min(64, cols-addr_w-32))
|
||||
txdots = "..." if tx_w < 64 else ""
|
||||
fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s"
|
||||
table_hdr = fs % ("Num","TX id Vout","","Address","Amount (BTC)",
|
||||
"Age(d)" if show_days else "Conf.")
|
||||
|
||||
unsp = deepcopy(unspent)
|
||||
for i in unsp: i.skip = ""
|
||||
if group and (sort == "address" or sort == "txid"):
|
||||
for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
|
||||
if sort == "address" and a.address == b.address: b.skip = "addr"
|
||||
elif sort == "txid" and a.txid == b.txid: b.skip = "txid"
|
||||
|
||||
for i in unsp:
|
||||
amt = str(trim_exponent(i.amount))
|
||||
lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
|
||||
i.amt = " "*lfill + amt
|
||||
i.days = int(i.confirmations * g.mins_per_block / (60*24))
|
||||
i.age = i.days if show_days else i.confirmations
|
||||
i.mmid,i.label = parse_mmgen_label(i.account)
|
||||
|
||||
if i.skip == "addr":
|
||||
i.addr = "|" + "." * 33
|
||||
else:
|
||||
if show_mmaddr:
|
||||
dots = ".." if btaddr_w < len(i.address) else ""
|
||||
i.addr = "%s%s %s" % (
|
||||
i.address[:btaddr_w-len(dots)],
|
||||
dots,
|
||||
i.account[:acct_w])
|
||||
else:
|
||||
i.addr = i.address
|
||||
|
||||
i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
|
||||
else i.txid[:tx_w-len(txdots)]+txdots
|
||||
|
||||
sort_info = ["reverse"] if reverse else []
|
||||
sort_info.append(sort if sort else "unsorted")
|
||||
if group and (sort == "address" or sort == "txid"):
|
||||
sort_info.append("grouped")
|
||||
|
||||
out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
|
||||
out += [fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.age)
|
||||
for n,i in enumerate(unsp)]
|
||||
|
||||
msg("\n".join(out) +"\n\n" + write_to_file_msg + options_msg)
|
||||
write_to_file_msg = ""
|
||||
|
||||
skip_prompt = False
|
||||
|
||||
while True:
|
||||
reply = get_char(prompt, immed_chars="atDdAMrgmeqpvw")
|
||||
|
||||
if reply == 'a': unspent.sort(key=s_amt); sort = "amount"
|
||||
elif reply == 't': unspent.sort(key=s_txid); sort = "txid"
|
||||
elif reply == 'D': show_days = False if show_days else True
|
||||
elif reply == 'd': unspent.sort(key=s_addr); sort = "address"
|
||||
elif reply == 'A': unspent.sort(key=s_age); sort = "age"
|
||||
elif reply == 'M':
|
||||
unspent.sort(key=s_mmgen); sort = "mmgen"
|
||||
show_mmaddr = True
|
||||
elif reply == 'r':
|
||||
unspent.reverse()
|
||||
reverse = False if reverse else True
|
||||
elif reply == 'g': group = False if group else True
|
||||
elif reply == 'm': show_mmaddr = False if show_mmaddr else True
|
||||
elif reply == 'e': pass
|
||||
elif reply == 'q': pass
|
||||
elif reply == 'p':
|
||||
d = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
of = "listunspent[%s].out" % ",".join(sort_info)
|
||||
write_to_file(of, d, opts,"",False,False)
|
||||
write_to_file_msg = "Data written to '%s'\n\n" % of
|
||||
elif reply == 'v':
|
||||
do_pager("\n".join(out))
|
||||
continue
|
||||
elif reply == 'w':
|
||||
data = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
do_pager(data)
|
||||
continue
|
||||
else:
|
||||
msg("\nInvalid input")
|
||||
continue
|
||||
|
||||
break
|
||||
|
||||
msg("\n")
|
||||
if reply == 'q': break
|
||||
|
||||
return tuple(unspent)
|
||||
|
||||
|
||||
def parse_mmgen_label(s,check_label_len=False):
|
||||
l = split2(s)
|
||||
if not is_mmgen_addr(l[0]): return "",s
|
||||
if check_label_len: check_addr_label(l[1])
|
||||
return tuple(l)
|
||||
|
||||
def is_mmgen_seed_id(s):
|
||||
import re
|
||||
return re.match(r"^[0123456789ABCDEF]{8}$",s) is not None
|
||||
|
||||
def is_mmgen_idx(s):
|
||||
import re
|
||||
m = g.mmgen_idx_max_digits
|
||||
return re.match(r"^[0123456789]{1,"+str(m)+r"}$",s) is not None
|
||||
|
||||
def is_mmgen_addr(s):
|
||||
seed_id,idx = split2(s,":")
|
||||
return is_mmgen_seed_id(seed_id) and is_mmgen_idx(idx)
|
||||
|
||||
def is_btc_addr(s):
|
||||
from mmgen.bitcoin import verify_addr
|
||||
return verify_addr(s)
|
||||
|
||||
def is_b58_str(s):
|
||||
from mmgen.bitcoin import b58a
|
||||
for ch in s:
|
||||
if ch not in b58a: return False
|
||||
return True
|
||||
|
||||
def is_btc_key(s):
|
||||
if s == "": return False
|
||||
compressed = not s[0] == '5'
|
||||
from mmgen.bitcoin import wiftohex
|
||||
return wiftohex(s,compressed) is not False
|
||||
|
||||
def wiftoaddr(s):
|
||||
if s == "": return False
|
||||
compressed = not s[0] == '5'
|
||||
from mmgen.bitcoin import wiftohex,privnum2addr
|
||||
hex_key = wiftohex(s,compressed)
|
||||
if not hex_key: return False
|
||||
return privnum2addr(int(hex_key,16),compressed)
|
||||
|
||||
def is_valid_tx_comment(s, verbose=True):
|
||||
if len(s) > g.max_tx_comment_len:
|
||||
if verbose: msg("Invalid transaction comment (longer than %s characters)" %
|
||||
g.max_tx_comment_len)
|
||||
return False
|
||||
try: s.decode("utf8")
|
||||
except:
|
||||
if verbose: msg("Invalid transaction comment (not UTF-8)")
|
||||
return False
|
||||
else: return True
|
||||
|
||||
def check_addr_label(label):
|
||||
|
||||
if len(label) > g.max_addr_label_len:
|
||||
msg("'%s': overlong label (length must be <=%s)" %
|
||||
(label,g.max_addr_label_len))
|
||||
sys.exit(3)
|
||||
|
||||
for ch in label:
|
||||
if ch not in g.addr_label_symbols:
|
||||
msg("""
|
||||
"%s": illegal character in label "%s".
|
||||
Only ASCII printable characters are permitted.
|
||||
""".strip() % (ch,label))
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False):
|
||||
|
||||
|
|
@ -343,7 +150,7 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False):
|
|||
total_in += j['amount']
|
||||
addr = j['address']
|
||||
mmid,label = parse_mmgen_label(j['account']) \
|
||||
if 'account' in j else ("","")
|
||||
if 'account' in j else ("","")
|
||||
mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
|
||||
|
||||
for d in (
|
||||
|
|
@ -378,10 +185,13 @@ def view_tx_data(c,inputs_data,tx_hex,b2m_map,comment,metadata,pager=False):
|
|||
|
||||
o = out.encode("utf8")
|
||||
if pager: do_pager(o)
|
||||
else: print "\n"+o
|
||||
else:
|
||||
print "\n"+o
|
||||
get_char("Press any key to continue: ")
|
||||
msg("")
|
||||
|
||||
|
||||
def parse_tx_data(tx_data,infile):
|
||||
def parse_tx_file(tx_data,infile):
|
||||
|
||||
err_str,err_fmt = "","Invalid %s in transaction file"
|
||||
|
||||
|
|
@ -424,164 +234,34 @@ def parse_tx_data(tx_data,infile):
|
|||
return metadata.split(),tx_hex,inputs_data,outputs_data,comment
|
||||
|
||||
|
||||
def select_outputs(unspent,prompt):
|
||||
|
||||
while True:
|
||||
reply = my_raw_input(prompt).strip()
|
||||
|
||||
if not reply: continue
|
||||
|
||||
from mmgen.util import parse_address_list
|
||||
selected = parse_address_list(reply,sep=None)
|
||||
|
||||
if not selected: continue
|
||||
|
||||
if selected[-1] > len(unspent):
|
||||
msg("Inputs must be less than %s" % len(unspent))
|
||||
continue
|
||||
|
||||
return selected
|
||||
|
||||
def is_mmgen_seed_id(s):
|
||||
import re
|
||||
return True if re.match(r"^[0123456789ABCDEF]{8}$",s) else False
|
||||
|
||||
def is_mmgen_idx(s):
|
||||
import re
|
||||
m = g.mmgen_idx_max_digits
|
||||
return True if re.match(r"^[0123456789]{1,"+str(m)+r"}$",s) else False
|
||||
|
||||
def is_mmgen_addr(s):
|
||||
seed_id,idx = split2(s,":")
|
||||
return is_mmgen_seed_id(seed_id) and is_mmgen_idx(idx)
|
||||
|
||||
def is_btc_addr(s):
|
||||
from mmgen.bitcoin import verify_addr
|
||||
return verify_addr(s)
|
||||
|
||||
def is_b58_str(s):
|
||||
from mmgen.bitcoin import b58a
|
||||
for ch in s:
|
||||
if ch not in b58a: return False
|
||||
return True
|
||||
|
||||
def is_btc_key(s):
|
||||
if s == "": return False
|
||||
compressed = False if s[0] == '5' else True
|
||||
from mmgen.bitcoin import wiftohex
|
||||
return True if wiftohex(s,compressed) else False
|
||||
|
||||
def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data):
|
||||
|
||||
# Don't want to create a new object, so use append()
|
||||
if not acct_data:
|
||||
for i in c.listaccounts(minconf=0,includeWatchonly=True):
|
||||
acct_data.append(i)
|
||||
|
||||
for acct in acct_data:
|
||||
m,comment = parse_mmgen_label(acct)
|
||||
if m == mmaddr:
|
||||
addrlist = c.getaddressesbyaccount(acct)
|
||||
if len(addrlist) == 1:
|
||||
return addrlist[0],comment
|
||||
else:
|
||||
msg(txmsg['too_many_acct_addresses'] % acct); sys.exit(2)
|
||||
|
||||
return "",""
|
||||
def get_wif2addr_f():
|
||||
if g.no_keyconv: return wiftoaddr
|
||||
from mmgen.addr import test_for_keyconv
|
||||
return wiftoaddr_keyconv if test_for_keyconv() else wiftoaddr
|
||||
|
||||
|
||||
def mmaddr2btcaddr_addrfile(mmaddr,addr_data,silent=False):
|
||||
|
||||
mmid,mmidx = mmaddr.split(":")
|
||||
|
||||
for ad in addr_data:
|
||||
if mmid == ad[0]:
|
||||
for j in ad[1]:
|
||||
if j[0] == mmidx:
|
||||
if not silent:
|
||||
msg(txmsg['addrfile_warn_msg'].format(mmaddr=mmaddr))
|
||||
if not keypress_confirm("Continue anyway?"):
|
||||
sys.exit(1)
|
||||
return j[1:] if len(j) == 3 else (j[1],"")
|
||||
|
||||
if silent: return "",""
|
||||
else:
|
||||
msg(txmsg['addrfile_fail_msg'].format(mmaddr=mmaddr))
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def check_mmgen_to_btc_addr_mappings(mmgen_inputs,b2m_map,infiles,saved_seeds,opts):
|
||||
in_maplist = [(i['account'].split()[0],i['address']) for i in mmgen_inputs]
|
||||
out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
|
||||
|
||||
for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
|
||||
if not maplist: continue
|
||||
qmsg("Checking MMGen -> BTC address mappings for %s" % label)
|
||||
pairs = get_keys_for_mmgen_addrs([i[0] for i in maplist],
|
||||
infiles,saved_seeds,opts,gen_pairs=True)
|
||||
for a,b in zip(sorted(pairs),sorted(maplist)):
|
||||
if a != b:
|
||||
msg(txmsg['mapping_error'] % (" ".join(a)," ".join(b)))
|
||||
sys.exit(3)
|
||||
|
||||
qmsg("Address mappings OK")
|
||||
|
||||
|
||||
def check_addr_label(label):
|
||||
|
||||
if len(label) > g.max_addr_label_len:
|
||||
msg("'%s': overlong label (length must be <=%s)" %
|
||||
(label,g.max_addr_label_len))
|
||||
sys.exit(3)
|
||||
|
||||
for ch in label:
|
||||
if ch not in g.addr_label_symbols:
|
||||
msg("""
|
||||
"%s": illegal character in label "%s".
|
||||
Only ASCII printable characters are permitted.
|
||||
""".strip() % (ch,label))
|
||||
sys.exit(3)
|
||||
|
||||
def make_addr_data_chksum(addr_data):
|
||||
def make_addr_data_chksum(adata,keys=False):
|
||||
nchars = 24
|
||||
return make_chksum_N(
|
||||
" ".join(["{} {}".format(*d[:2]) for d in addr_data]), nchars, sep=True
|
||||
)
|
||||
return make_chksum_N(" ".join([" ".join(
|
||||
[str(n),d[0],d[2]] if keys else [str(n),d[0]]
|
||||
) for n,d in adata]), nchars, sep=True)
|
||||
|
||||
def check_addr_data_hash(seed_id,addr_data):
|
||||
|
||||
def get_addr_data_hash(e,keys=False):
|
||||
def s_addrdata(a): return int(a[0])
|
||||
addr_data_chksum = make_addr_data_chksum(sorted(addr_data,key=s_addrdata))
|
||||
from mmgen.addr import fmt_addr_idxs
|
||||
fl = fmt_addr_idxs([int(a[0]) for a in addr_data])
|
||||
qmsg_r("Computed checksum for addr data {}[{}]: ".format(seed_id,fl))
|
||||
msg(addr_data_chksum)
|
||||
qmsg("Check this value against your records")
|
||||
adata = [(k,e[k]) for k in e.keys()]
|
||||
return make_addr_data_chksum(sorted(adata,key=s_addrdata),keys)
|
||||
|
||||
def parse_addrs_file(f):
|
||||
|
||||
lines = get_lines_from_file(f,"address data",trim_comments=True)
|
||||
def _parse_addrfile_body(lines,keys=False,check=False):
|
||||
|
||||
try:
|
||||
seed_id,obrace = lines[0].split()
|
||||
except:
|
||||
msg("Invalid first line: '%s'" % lines[0])
|
||||
sys.exit(3)
|
||||
|
||||
cbrace = lines[-1]
|
||||
|
||||
if obrace != '{':
|
||||
msg("'%s': invalid first line" % lines[0])
|
||||
elif cbrace != '}':
|
||||
msg("'%s': invalid last line" % cbrace)
|
||||
elif not is_mmgen_seed_id(seed_id):
|
||||
msg("'%s': invalid Seed ID" % seed_id)
|
||||
else:
|
||||
addr_data = []
|
||||
for i in lines[1:-1]:
|
||||
d = i.split(None,2)
|
||||
def parse_addr_lines(lines):
|
||||
ret = []
|
||||
for l in lines:
|
||||
d = l.split(None,2)
|
||||
|
||||
if not is_mmgen_idx(d[0]):
|
||||
msg("'%s': invalid address num. in line: %s" % (d[0],d))
|
||||
msg("'%s': invalid address num. in line: '%s'" % (d[0],l))
|
||||
sys.exit(3)
|
||||
|
||||
if not is_btc_addr(d[1]):
|
||||
|
|
@ -589,210 +269,112 @@ def parse_addrs_file(f):
|
|||
sys.exit(3)
|
||||
|
||||
if len(d) == 3:
|
||||
check_addr_label(d[2])
|
||||
|
||||
addr_data.append(tuple(d))
|
||||
|
||||
check_addr_data_hash(seed_id,addr_data)
|
||||
|
||||
return seed_id,addr_data
|
||||
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def sign_transaction(c,tx_hex,tx_num_str,sig_data,keys=None):
|
||||
|
||||
if keys:
|
||||
qmsg("%s keys total" % len(keys))
|
||||
if g.debug: print "Keys:\n %s" % "\n ".join(keys)
|
||||
|
||||
msg_r("Signing transaction{}...".format(tx_num_str))
|
||||
from mmgen.rpc import exceptions
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
|
||||
except exceptions.InvalidAddressOrKey:
|
||||
msg("failed\nInvalid address or key")
|
||||
sys.exit(3)
|
||||
|
||||
return sig_tx
|
||||
|
||||
def get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts):
|
||||
|
||||
if seed_id in saved_seeds.keys():
|
||||
return saved_seeds[seed_id]
|
||||
|
||||
while True:
|
||||
if infiles:
|
||||
seed = get_seed_retry(infiles.pop(0),opts)
|
||||
elif "from_brain" in opts or "from_mnemonic" in opts \
|
||||
or "from_seed" in opts or "from_incog" in opts:
|
||||
msg("Need data for seed ID %s" % seed_id)
|
||||
seed = get_seed_retry("",opts)
|
||||
msg("User input produced seed ID %s" % make_chksum_8(seed))
|
||||
else:
|
||||
msg("ERROR: No seed source found for seed ID: %s" % seed_id)
|
||||
sys.exit(2)
|
||||
|
||||
s_id = make_chksum_8(seed)
|
||||
saved_seeds[s_id] = seed
|
||||
|
||||
if s_id == seed_id: return seed
|
||||
|
||||
|
||||
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds,opts,gen_pairs=False):
|
||||
|
||||
seed_ids = list(set([i[:8] for i in mmgen_addrs]))
|
||||
ret = []
|
||||
|
||||
for seed_id in seed_ids:
|
||||
# Returns only if seed is found
|
||||
seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts)
|
||||
|
||||
addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
|
||||
from mmgen.addr import generate_addrs
|
||||
if gen_pairs:
|
||||
ret += [("{}:{}".format(seed_id,i.num),i.addr)
|
||||
for i in generate_addrs(seed,addr_ids,
|
||||
{'gen_what':["addrs"]})]
|
||||
else:
|
||||
ret += [i.wif for i in generate_addrs(seed,addr_ids,
|
||||
{'gen_what':["keys"]})]
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def sign_tx_with_bitcoind_wallet(c,tx_hex,tx_num_str,sig_data,keys,opts):
|
||||
|
||||
try:
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
except:
|
||||
from mmgen.rpc import exceptions
|
||||
msg("Using keys in wallet.dat as per user request")
|
||||
prompt = "Enter passphrase for bitcoind wallet: "
|
||||
while True:
|
||||
passwd = get_bitcoind_passphrase(prompt,opts)
|
||||
|
||||
try:
|
||||
c.walletpassphrase(passwd, 9999)
|
||||
except exceptions.WalletPassphraseIncorrect:
|
||||
msg("Passphrase incorrect")
|
||||
comment = d[2]
|
||||
check_addr_label(comment)
|
||||
else:
|
||||
msg("Passphrase OK"); break
|
||||
comment = ""
|
||||
|
||||
sig_tx = sign_transaction(c,tx_hex,tx_num_str,sig_data,keys)
|
||||
ret.append((d[0],d[1],comment))
|
||||
|
||||
msg("Locking wallet")
|
||||
try:
|
||||
c.walletlock()
|
||||
except:
|
||||
msg("Failed to lock wallet")
|
||||
return ret
|
||||
|
||||
return sig_tx
|
||||
def parse_key_lines(lines):
|
||||
ret = []
|
||||
for l in lines:
|
||||
d = l.split(None,2)
|
||||
|
||||
if d[0] != "wif:":
|
||||
msg("Invalid key line in file: '%s'" % l)
|
||||
sys.exit(3)
|
||||
|
||||
if not is_btc_key(d[1]):
|
||||
msg("'%s': invalid Bitcoin key" % d[1])
|
||||
sys.exit(3)
|
||||
|
||||
ret.append(d[1])
|
||||
|
||||
return ret
|
||||
|
||||
z = len(lines) / 2
|
||||
if keys:
|
||||
adata = parse_addr_lines([lines[i*2] for i in range(z)])
|
||||
kdata = parse_key_lines([lines[i*2+1] for i in range(z)])
|
||||
if len(adata) != len(kdata):
|
||||
msg("Odd number of lines in key file")
|
||||
sys.exit(2)
|
||||
if check or keypress_confirm("Check key-to-address validity?"):
|
||||
wif2addr_f = get_wif2addr_f()
|
||||
for i in range(z):
|
||||
msg_r("\rVerifying keys %s/%s" % (i+1,z))
|
||||
if adata[i][1] != wif2addr_f(kdata[i]):
|
||||
msg("Key doesn't match address!\n %s\n %s" %
|
||||
kdata[i],adata[i][1])
|
||||
sys.exit(2)
|
||||
msg(" - done")
|
||||
return [adata[i] + (kdata[i],) for i in range(z)]
|
||||
else:
|
||||
return parse_addr_lines(lines)
|
||||
|
||||
|
||||
def preverify_keys(addrs_in, keys_in, mm_inputs):
|
||||
def parse_addrfile(f,addr_data,keys=False):
|
||||
return parse_addrfile_lines(
|
||||
get_lines_from_file(f,"address data",trim_comments=True),
|
||||
addr_data,keys)
|
||||
|
||||
addrs,keys = set(addrs_in),set(keys_in)
|
||||
|
||||
import mmgen.bitcoin as b
|
||||
|
||||
qmsg_r('Checking that user-supplied key list contains valid keys...')
|
||||
|
||||
invalid_keys = [k for k in keys if not is_btc_key(k)]
|
||||
if invalid_keys:
|
||||
s = "" if len(invalid_keys) == 1 else "s"
|
||||
msg("\n%s/%s invalid key%s in keylist!\n" % (len(invalid_keys),len(keys),s))
|
||||
sys.exit(2)
|
||||
|
||||
qmsg("OK")
|
||||
|
||||
# Check that keys match addresses:
|
||||
msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
|
||||
def parse_addrfile_lines(lines,addr_data,keys=False,exit_on_error=True):
|
||||
|
||||
try:
|
||||
for n,k in enumerate(keys,1):
|
||||
msg_r("\rkey %s of %s" % (n,len(keys)))
|
||||
c = False if k[0] == '5' else True
|
||||
hexkey = b.wiftohex(k,compressed=c)
|
||||
addr = b.privnum2addr(int(hexkey,16),compressed=c)
|
||||
if addr in addrs:
|
||||
addrs.remove(addr)
|
||||
if not addrs: break
|
||||
except KeyboardInterrupt:
|
||||
msg("\nSkipping")
|
||||
else:
|
||||
msg("")
|
||||
if addrs:
|
||||
mms = dict([(i['address'],i['account'].split()[0])
|
||||
for i in mm_inputs if i['address'] in addrs])
|
||||
s = "" if len(addrs) == 1 else "es"
|
||||
msg(
|
||||
"Cannot sign transaction. No keys found for the following address%s:"%s)
|
||||
for a in sorted(addrs):
|
||||
print " %s%s" % (a, " ({})".format(mms[a]) if a in mms else "")
|
||||
sys.exit(2)
|
||||
else:
|
||||
extra_keys = len(keys) - len(set(addrs_in))
|
||||
if extra_keys > 0:
|
||||
s = "" if extra_keys == 1 else "s"
|
||||
msg("%s extra key%s found" % (extra_keys,s))
|
||||
|
||||
|
||||
|
||||
def missing_keys_errormsg(other_addrs):
|
||||
msg("""
|
||||
A key file must be supplied (or use the "-w" option) for the following
|
||||
non-mmgen address%s:
|
||||
""".strip() % ("" if len(other_addrs) == 1 else "es"))
|
||||
print " %s" % "\n ".join([i['address'] for i in other_addrs])
|
||||
|
||||
|
||||
def check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles):
|
||||
addr_data = [parse_addrs_file(a) for a in addrfiles]
|
||||
in_maplist = [(i['account'].split()[0],i['address']) for i in mmgen_inputs]
|
||||
out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
|
||||
|
||||
missing,wrong = [],[]
|
||||
for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
|
||||
qmsg("Checking MMGen -> BTC address mappings for %s" % label)
|
||||
for i in maplist:
|
||||
btaddr = mmaddr2btcaddr_addrfile(i[0],addr_data,silent=True)[0]
|
||||
if not btaddr: missing.append(i[0])
|
||||
elif btaddr != i[1]: wrong.append((i[0],i[1],btaddr))
|
||||
|
||||
if wrong:
|
||||
fs = " {:11} {:35} {}"
|
||||
msg("ERROR: The following address mappings did not match!")
|
||||
msg(fs.format("MMGen addr","In TX file:","In address file:"))
|
||||
for w in wrong: msg(fs.format(*w))
|
||||
sys.exit(3)
|
||||
|
||||
if missing:
|
||||
confirm_or_exit(txmsg['missing_mappings'] %
|
||||
" ".join(missing),"continue")
|
||||
else: qmsg("Address mappings OK")
|
||||
|
||||
|
||||
def is_valid_tx_comment(s, verbose=True):
|
||||
if len(s) > g.max_tx_comment_len:
|
||||
if verbose: msg("Invalid transaction comment (longer than %s characters)" %
|
||||
g.max_tx_comment_len)
|
||||
return False
|
||||
try: s.decode("utf8")
|
||||
seed_id,obrace = lines[0].split()
|
||||
except:
|
||||
if verbose: msg("Invalid transaction comment (not UTF-8)")
|
||||
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(seed_id):
|
||||
errmsg = "'%s': invalid Seed ID" % seed_id
|
||||
else:
|
||||
ldata = _parse_addrfile_body(lines[1:-1],keys)
|
||||
if seed_id not in addr_data: addr_data[seed_id] = {}
|
||||
for l in ldata:
|
||||
addr_data[seed_id][l[0]] = l[1:]
|
||||
chk = get_addr_data_hash(addr_data[seed_id],keys)
|
||||
from mmgen.addr import fmt_addr_idxs
|
||||
fl = fmt_addr_idxs([int(i) for i in addr_data[seed_id].keys()])
|
||||
w = "key" if keys else "addr"
|
||||
qmsg_r("Computed checksum for "+w+" data ",w.capitalize()+" checksum ")
|
||||
msg("{}[{}]: {}".format(seed_id,fl,chk))
|
||||
qmsg("Check this value against your records")
|
||||
return True
|
||||
|
||||
if exit_on_error:
|
||||
msg(errmsg)
|
||||
sys.exit(3)
|
||||
else:
|
||||
return False
|
||||
else: return True
|
||||
|
||||
|
||||
def parse_keyaddr_file(infile,addr_data):
|
||||
d = get_data_from_file(infile,"%s key-address file data" % g.proj_name)
|
||||
enc_ext = get_extension(infile) == g.mmenc_ext
|
||||
if enc_ext or not is_utf8(d):
|
||||
m = "Decrypting" if enc_ext else "Attempting to decrypt"
|
||||
msg("%s key-address file %s" % (m,infile))
|
||||
from crypto import mmgen_decrypt_retry
|
||||
d = mmgen_decrypt_retry(d,"key-address file")
|
||||
parse_addrfile_lines(remove_comments(d.split("\n")),addr_data,True,False)
|
||||
|
||||
|
||||
def get_tx_comment_from_file(infile):
|
||||
s = get_data_from_file(infile,"transaction comment")
|
||||
if is_valid_tx_comment(s, verbose=True):
|
||||
return s.decode("utf8").strip()
|
||||
else: return False
|
||||
|
||||
else:
|
||||
sys.exit(2)
|
||||
|
||||
def get_tx_comment_from_user(comment=""):
|
||||
|
||||
try:
|
||||
while True:
|
||||
s = my_raw_input("Comment: ",insert_txt=comment.encode("utf8"))
|
||||
|
|
@ -800,12 +382,68 @@ def get_tx_comment_from_user(comment=""):
|
|||
if is_valid_tx_comment(s, verbose=True):
|
||||
return s.decode("utf8")
|
||||
except KeyboardInterrupt:
|
||||
msg("User interrupt")
|
||||
return False
|
||||
|
||||
msg("User interrupt")
|
||||
return False
|
||||
|
||||
def make_tx_data(metadata_fmt, tx_hex, inputs_data, b2m_map, comment):
|
||||
from mmgen.bitcoin import b58encode
|
||||
s = (b58encode(comment.encode("utf8")),) if comment else ()
|
||||
lines = (metadata_fmt, tx_hex, repr(inputs_data), repr(b2m_map)) + s
|
||||
return "\n".join(lines)+"\n"
|
||||
|
||||
def mmaddr2btcaddr_addrdata(mmaddr,addr_data,source=""):
|
||||
seed_id,idx = mmaddr.split(":")
|
||||
if seed_id in addr_data:
|
||||
if idx in addr_data[seed_id]:
|
||||
vmsg("%s -> %s%s" % (mmaddr,addr_data[seed_id][idx][0],
|
||||
" (from "+source+")" if source else ""))
|
||||
return addr_data[seed_id][idx]
|
||||
|
||||
return "",""
|
||||
|
||||
def get_bitcoind_cfg_options(cfg_keys):
|
||||
|
||||
if "HOME" in os.environ: # Linux
|
||||
homedir,datadir = os.environ["HOME"],".bitcoin"
|
||||
elif "HOMEPATH" in os.environ: # Windows:
|
||||
homedir,data_dir = os.environ["HOMEPATH"],r"Application Data\Bitcoin"
|
||||
else:
|
||||
msg("Neither $HOME nor %HOMEPATH% are set")
|
||||
msg("Don't know where to look for 'bitcoin.conf'")
|
||||
sys.exit(3)
|
||||
|
||||
cfg_file = os.sep.join((homedir, datadir, "bitcoin.conf"))
|
||||
|
||||
cfg = dict([(k,v) for k,v in [split2(line.translate(None,"\t "),"=")
|
||||
for line in get_lines_from_file(cfg_file)] if k in cfg_keys])
|
||||
|
||||
for k in set(cfg_keys) - set(cfg.keys()):
|
||||
msg("Configuration option '%s' must be set in %s" % (k,cfg_file))
|
||||
sys.exit(2)
|
||||
|
||||
return cfg
|
||||
|
||||
def connect_to_bitcoind():
|
||||
|
||||
host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
|
||||
cfg = get_bitcoind_cfg_options((user,passwd))
|
||||
|
||||
import mmgen.rpc.connection
|
||||
f = mmgen.rpc.connection.BitcoinConnection
|
||||
|
||||
try:
|
||||
c = f(cfg[user],cfg[passwd],host,port)
|
||||
except:
|
||||
msg("Unable to establish RPC connection with bitcoind")
|
||||
sys.exit(2)
|
||||
|
||||
return c
|
||||
|
||||
|
||||
def wiftoaddr_keyconv(wif):
|
||||
from subprocess import Popen, PIPE
|
||||
if wif[0] == '5':
|
||||
return Popen(["keyconv", wif],
|
||||
stdout=PIPE).stdout.readline().split()[1]
|
||||
else:
|
||||
return wiftoaddr(wif)
|
||||
|
|
|
|||
151
mmgen/util.py
151
mmgen/util.py
|
|
@ -28,74 +28,33 @@ import mmgen.config as g
|
|||
|
||||
def msg(s): sys.stderr.write(s + "\n")
|
||||
def msg_r(s): sys.stderr.write(s)
|
||||
def qmsg(s,alt=""):
|
||||
def qmsg(s,alt=False):
|
||||
if g.quiet:
|
||||
if alt: sys.stderr.write(alt + "\n")
|
||||
if alt != False: sys.stderr.write(alt + "\n")
|
||||
else: sys.stderr.write(s + "\n")
|
||||
def qmsg_r(s,alt=""):
|
||||
def qmsg_r(s,alt=False):
|
||||
if g.quiet:
|
||||
if alt: sys.stderr.write(alt)
|
||||
if alt != False: sys.stderr.write(alt)
|
||||
else: sys.stderr.write(s)
|
||||
def vmsg(s):
|
||||
if g.verbose: sys.stderr.write(s + "\n")
|
||||
def vmsg_r(s):
|
||||
if g.verbose: sys.stderr.write(s)
|
||||
|
||||
cmessages = {
|
||||
'null': "",
|
||||
'incog_iv_id': """
|
||||
If you know your Incog ID, check it against the value above. If it's
|
||||
incorrect, then your incognito data is invalid.
|
||||
""",
|
||||
'incog_iv_id_hidden': """
|
||||
If you know your Incog ID, check it against the value above. If it's
|
||||
incorrect, then your incognito data is invalid or you've supplied
|
||||
an incorrect offset.
|
||||
""",
|
||||
'incog_key_id': """
|
||||
Check that the generated seed ID is correct. If it's not, then your
|
||||
password or hash preset is incorrect or incognito data is corrupted.
|
||||
""",
|
||||
'incog_key_id_hidden': """
|
||||
Check that the generated seed ID is correct. If it's not, then your
|
||||
password or hash preset is incorrect or incognito data is corrupted.
|
||||
If the key ID is correct but the seed ID is not, then you might have
|
||||
chosen an incorrect seed length.
|
||||
""",
|
||||
'unencrypted_secret_keys': """
|
||||
This program generates secret keys from your {} seed, outputting them in
|
||||
UNENCRYPTED form. Generate only the key(s) you need and guard them carefully.
|
||||
""".format(g.proj_name),
|
||||
'brain_warning': """
|
||||
############################## EXPERTS ONLY! ##############################
|
||||
def suf(arg,what):
|
||||
t = type(arg)
|
||||
if t == int:
|
||||
n = arg
|
||||
elif t == list or t == tuple or t == set:
|
||||
n = len(arg)
|
||||
else:
|
||||
msg("%s: invalid parameter" % arg)
|
||||
return ""
|
||||
|
||||
A brainwallet will be secure only if you really know what you're doing and
|
||||
have put much care into its creation. {} assumes no responsibility for
|
||||
coins stolen as a result of a poorly crafted brainwallet passphrase.
|
||||
|
||||
A key will be generated from your passphrase using the parameters requested
|
||||
by you: seed length {}, hash preset '{}'. For brainwallets it's highly
|
||||
recommended to use one of the higher-numbered presets
|
||||
|
||||
Remember the seed length and hash preset parameters you've specified. To
|
||||
generate the correct keys/addresses associated with this passphrase in the
|
||||
future, you must continue using these same parameters
|
||||
""",
|
||||
'usr_rand_notice': """
|
||||
You've chosen to not fully trust your OS's random number generator and provide
|
||||
some additional entropy of your own. Please type %s symbols on your keyboard.
|
||||
Type slowly and choose your symbols carefully for maximum randomness. Try to
|
||||
use both upper and lowercase as well as punctuation and numerals. What you
|
||||
type will not be displayed on the screen. Note that the timings between your
|
||||
keystrokes will also be used as a source of randomness.
|
||||
""",
|
||||
'choose_wallet_passphrase': """
|
||||
Now you must choose a passphrase to encrypt the wallet 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()
|
||||
}
|
||||
if what in "a":
|
||||
return "" if n == 1 else "es"
|
||||
if what in "k":
|
||||
return "" if n == 1 else "s"
|
||||
|
||||
def get_extension(f):
|
||||
import os
|
||||
|
|
@ -138,6 +97,11 @@ def _is_hex(s):
|
|||
except: return False
|
||||
else: return True
|
||||
|
||||
def is_utf8(s):
|
||||
try: s.decode("utf8")
|
||||
except: return False
|
||||
else: return True
|
||||
|
||||
def match_ext(addr,ext):
|
||||
return addr.split(".")[-1] == ext
|
||||
|
||||
|
|
@ -149,7 +113,7 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
|
|||
r = 1 if len(data) % gw else 0
|
||||
return "".join(
|
||||
[
|
||||
("" if (line_nums == False or i % cols) else "%03i: " % (i/cols)) +
|
||||
("" if (line_nums == False or i % cols) else "{:06x}: ".format(i*gw)) +
|
||||
hexlify(data[i*gw:i*gw+gw]) +
|
||||
(" " if (i+1) % cols else "\n")
|
||||
for i in range(len(data)/gw + r)
|
||||
|
|
@ -158,7 +122,8 @@ def pretty_hexdump(data,gw=2,cols=8,line_nums=False):
|
|||
|
||||
def decode_pretty_hexdump(data):
|
||||
import re
|
||||
lines = [re.sub('^\d+:\s+','',l) for l in data.split("\n")]
|
||||
from string import hexdigits
|
||||
lines = [re.sub('^['+hexdigits+']+:\s+','',l) for l in data.split("\n")]
|
||||
return unhexlify("".join(("".join(lines).split())))
|
||||
|
||||
def get_hash_params(hash_preset):
|
||||
|
|
@ -237,7 +202,6 @@ def check_infile(f): return check_file_type_and_access(f,"input file")
|
|||
def check_outfile(f): return check_file_type_and_access(f,"output file")
|
||||
def check_outdir(f): return check_file_type_and_access(f,"directory")
|
||||
|
||||
|
||||
def _validate_addr_num(n):
|
||||
|
||||
try: n = int(n)
|
||||
|
|
@ -258,7 +222,7 @@ def make_full_path(outdir,outfile):
|
|||
# os.path.join() doesn't work?
|
||||
|
||||
|
||||
def parse_address_list(arg,sep=","):
|
||||
def parse_addr_idxs(arg,sep=","):
|
||||
|
||||
ret = []
|
||||
|
||||
|
|
@ -278,7 +242,7 @@ def parse_address_list(arg,sep=","):
|
|||
if end < beg:
|
||||
msg("'%s-%s': end of range less than beginning" % (beg,end))
|
||||
return False
|
||||
for k in range(beg,end+1): ret.append(k)
|
||||
ret.extend(range(beg,end+1))
|
||||
else:
|
||||
msg("'%s': invalid argument for address range" % i)
|
||||
return False
|
||||
|
|
@ -327,11 +291,8 @@ def confirm_or_false(message, question, expect="YES"):
|
|||
p = question+" "+conf_msg if question[0].isupper() else \
|
||||
"Are you sure you want to %s?\n%s" % (question,conf_msg)
|
||||
|
||||
ret = True if my_raw_input(p).strip() == expect else False
|
||||
|
||||
vmsg("")
|
||||
return ret
|
||||
|
||||
return my_raw_input(p).strip() == expect
|
||||
|
||||
|
||||
def write_to_stdout(data, what, confirm=True):
|
||||
|
|
@ -341,7 +302,9 @@ def write_to_stdout(data, what, confirm=True):
|
|||
try:
|
||||
import os
|
||||
of = os.readlink("/proc/%d/fd/1" % os.getpid())
|
||||
msg("Redirecting output to file '%s'" % os.path.relpath(of))
|
||||
of_maybe = os.path.relpath(of)
|
||||
of = of if of_maybe.find(os.path.pardir) == 0 else of_maybe
|
||||
msg("Redirecting output to file '%s'" % of)
|
||||
except:
|
||||
msg("Redirecting output to file")
|
||||
sys.stdout.write(data)
|
||||
|
|
@ -383,8 +346,7 @@ def write_to_file_or_stdout(outfile, data, opts, what="data"):
|
|||
if 'stdout' in opts or not sys.stdout.isatty():
|
||||
write_to_stdout(data, what, confirm=True)
|
||||
else:
|
||||
confirm_overwrite = False if g.quiet else True
|
||||
write_to_file(outfile,data,opts,what,confirm_overwrite,True)
|
||||
write_to_file(outfile,data,opts,what,not g.quiet,True)
|
||||
|
||||
|
||||
from mmgen.bitcoin import b58decode_pad,b58encode_pad
|
||||
|
|
@ -441,10 +403,9 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
|||
seed_id,key_id,seed_len,hash_preset,g.wallet_ext)
|
||||
|
||||
d = "\n".join((chk,)+lines)+"\n"
|
||||
confirm_overwrite = False if g.quiet else True
|
||||
write_to_file(outfile,d,opts,"wallet",confirm_overwrite,True)
|
||||
write_to_file(outfile,d,opts,"wallet",not g.quiet,True)
|
||||
|
||||
if g.verbose:
|
||||
if g.debug:
|
||||
display_control_data(label,metadata,hash_preset,salt,enc_seed)
|
||||
|
||||
|
||||
|
|
@ -542,8 +503,7 @@ def get_data_from_wallet(infile,silent=False):
|
|||
|
||||
def _get_words_from_user(prompt, opts):
|
||||
# split() also strips
|
||||
words = my_raw_input(prompt,
|
||||
echo=True if 'echo_passphrase' in opts else False).split()
|
||||
words = my_raw_input(prompt, echo='echo_passphrase' in opts).split()
|
||||
if g.debug: print "Sanitized input: [%s]" % " ".join(words)
|
||||
return words
|
||||
|
||||
|
|
@ -610,7 +570,7 @@ def get_seed_from_seed_data(words):
|
|||
msg("Invalid b58 number: %s" % val)
|
||||
return False
|
||||
|
||||
vmsg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed)))
|
||||
msg("Valid seed data for seed ID %s" % make_chksum_8(seed))
|
||||
return seed
|
||||
else:
|
||||
msg("Invalid checksum for {} seed".format(g.proj_name))
|
||||
|
|
@ -643,8 +603,7 @@ def get_bitcoind_passphrase(prompt,opts):
|
|||
return get_data_from_file(opts['passwd_file'],
|
||||
"passphrase").strip("\r\n")
|
||||
else:
|
||||
return my_raw_input(prompt,
|
||||
echo=True if 'echo_passphrase' in opts else False)
|
||||
return my_raw_input(prompt, echo='echo_passphrase' in opts)
|
||||
|
||||
|
||||
def check_data_fits_file_at_offset(fname,offset,dlen,action):
|
||||
|
|
@ -664,43 +623,11 @@ def check_data_fits_file_at_offset(fname,offset,dlen,action):
|
|||
sys.exit(1)
|
||||
|
||||
|
||||
def get_hidden_incog_data(opts):
|
||||
# Already sanity-checked:
|
||||
fname,offset,seed_len = opts['from_incog_hidden'].split(",")
|
||||
qmsg("Getting hidden incog data from file '%s'" % fname)
|
||||
|
||||
dlen = g.aesctr_iv_len + g.salt_len + (int(seed_len)/8)
|
||||
|
||||
fsize = check_data_fits_file_at_offset(fname,int(offset),dlen,"read")
|
||||
|
||||
f = os.open(fname,os.O_RDONLY)
|
||||
os.lseek(f, int(offset), os.SEEK_SET)
|
||||
data = os.read(f, dlen)
|
||||
os.close(f)
|
||||
qmsg("Data read from file '%s' at offset %s" % (fname,offset),
|
||||
"Data read from file")
|
||||
return data
|
||||
|
||||
|
||||
def export_to_hidden_incog(incog_enc,opts):
|
||||
outfile,offset = opts['export_incog_hidden'].split(",") #Already sanity-checked
|
||||
if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
|
||||
|
||||
check_data_fits_file_at_offset(outfile,int(offset),len(incog_enc),"write")
|
||||
|
||||
if not g.quiet: confirm_or_exit("","alter file '%s'" % outfile)
|
||||
f = os.open(outfile,os.O_RDWR)
|
||||
os.lseek(f, int(offset), os.SEEK_SET)
|
||||
os.write(f, incog_enc)
|
||||
os.close(f)
|
||||
msg("Data written to file '%s' at offset %s" %
|
||||
(os.path.relpath(outfile),offset))
|
||||
|
||||
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)
|
||||
% (what,hp)
|
||||
while True:
|
||||
ret = my_raw_input(p)
|
||||
if ret:
|
||||
|
|
@ -761,5 +688,3 @@ def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
|
|||
|
||||
if verbose: msg("\nInvalid reply")
|
||||
else: msg_r("\r")
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue