New 'crypto.py' module for higher-level functions from 'util.py'.
'mmgen-keygen', 'mmgen-txsign': encrypted keylist support
This commit is contained in:
parent
5dc9bb15c5
commit
26aa2eb64b
14 changed files with 682 additions and 626 deletions
|
|
@ -19,7 +19,7 @@
|
|||
"""
|
||||
mmgen-addrgen: Generate a list or range of addresses from a mmgen
|
||||
deterministic wallet.
|
||||
Call as 'btc-keygen' to allow key generation as well.
|
||||
Call as 'btc-keygen' to allow key generation.
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
@ -28,6 +28,7 @@ import mmgen.config as g
|
|||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.util import *
|
||||
from mmgen.crypto import *
|
||||
from mmgen.addr import *
|
||||
from mmgen.tx import make_addr_data_chksum
|
||||
|
||||
|
|
@ -149,7 +150,7 @@ seed_id = make_chksum_8(seed)
|
|||
for l in (
|
||||
('flat_list', 'no_addresses'),
|
||||
('flat_list', 'b16'),
|
||||
): check_incompatible_opts(opts,l)
|
||||
): warn_incompatible_opts(opts,l)
|
||||
|
||||
opts['gen_what'] = \
|
||||
("addrs") if what == "addresses" else (
|
||||
|
|
@ -163,6 +164,17 @@ addr_data_str = format_addr_data(
|
|||
|
||||
outfile_base = "{}[{}]".format(seed_id, fmt_addr_idxs(addr_idxs))
|
||||
|
||||
if 'flat_list' in opts:
|
||||
confirm = False if g.quiet else True
|
||||
outfile = "%s.%s" % (outfile_base,g.keylist_ext)
|
||||
if (user_confirm("Encrypt key list?")):
|
||||
enc_data = mmgen_encrypt(addr_data_str,"",opts)
|
||||
outfile += "."+g.mmenc_ext
|
||||
write_to_file(outfile,enc_data,opts,"encrypted key list",confirm,True)
|
||||
else:
|
||||
write_to_file(outfile,addr_data_str,opts,"key list",confirm,True)
|
||||
sys.exit()
|
||||
|
||||
# Output data:
|
||||
if 'stdout' in opts:
|
||||
confirm = True if (what == "keys" and not g.quiet) else False
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ else:
|
|||
|
||||
if 'addrlist' in opts:
|
||||
lines = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
|
||||
remove_comments=True)
|
||||
trim_comments=True)
|
||||
addr_data += [(None,l) for l in lines]
|
||||
|
||||
from mmgen.bitcoin import verify_addr
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ mmgen-passchg: Change a mmgen deterministic wallet's passphrase, label or
|
|||
import sys
|
||||
from mmgen.Opts import *
|
||||
from mmgen.util import *
|
||||
from mmgen.crypto import *
|
||||
import mmgen.config as g
|
||||
|
||||
help_data = {
|
||||
|
|
|
|||
|
|
@ -86,8 +86,8 @@ help_data = {
|
|||
}
|
||||
|
||||
opts,cmd_args = parse_opts(sys.argv,help_data)
|
||||
from mmgen.Opts import check_incompatible_opts
|
||||
check_incompatible_opts(opts,('json','keys','addrs','keysforaddrs'))
|
||||
from mmgen.Opts import warn_incompatible_opts
|
||||
warn_incompatible_opts(opts,('json','keys','addrs','keysforaddrs'))
|
||||
|
||||
if len(cmd_args) == 1:
|
||||
from mmgen.util import check_infile
|
||||
|
|
@ -1653,7 +1653,7 @@ elif 'addrs' in opts:
|
|||
|
||||
elif 'keysforaddrs' in opts:
|
||||
from mmgen.util import get_lines_from_file
|
||||
usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",remove_comments=True))
|
||||
usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",trim_comments=True))
|
||||
data = [i['sec'] for i in json_db['keys'] if i['addr'] in usr_addrs]
|
||||
ext,what = "keys","private keys"
|
||||
if len(data) < len(usr_addrs):
|
||||
|
|
|
|||
16
mmgen-txsign
16
mmgen-txsign
|
|
@ -92,7 +92,7 @@ opts,infiles = parse_opts(sys.argv,help_data)
|
|||
for l in (
|
||||
('tx_id', 'info'),
|
||||
('keys_from_file','all_keys_from_file')
|
||||
): check_incompatible_opts(opts,l)
|
||||
): warn_incompatible_opts(opts,l)
|
||||
|
||||
if "quiet" in opts: g.quiet = True
|
||||
if 'from_incog_hex' in opts or 'from_incog_hidden' in opts:
|
||||
|
|
@ -114,8 +114,18 @@ infiles = list(set(infiles) - set(tx_files) - set(addrfiles))
|
|||
if not "info" in opts: do_license_msg(immed=True)
|
||||
|
||||
if 'keys_from_file' in opts:
|
||||
keys_from_file = get_lines_from_file(opts['keys_from_file'],"key data",
|
||||
remove_comments=True)
|
||||
from mmgen.crypto import mmgen_decrypt
|
||||
fn = opts['keys_from_file']
|
||||
if get_extension(fn) == g.mmenc_ext:
|
||||
enc_d = get_data_from_file(fn,"encrypted keylist")
|
||||
dec_d = mmgen_decrypt(enc_d,"",opts)
|
||||
if dec_d:
|
||||
keys_from_file = remove_comments(dec_d.split("\n"))
|
||||
else:
|
||||
msg("Decryption of encrypted keylist failed")
|
||||
sys.exit(2)
|
||||
else:
|
||||
keys_from_file = get_lines_from_file(fn,"key data",trim_comments=True)
|
||||
else: keys_from_file = []
|
||||
|
||||
for tx_file in tx_files:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import sys
|
|||
import mmgen.config as g
|
||||
from mmgen.Opts import *
|
||||
from mmgen.util import *
|
||||
from mmgen.crypto import get_seed_from_wallet,wallet_to_incog_data
|
||||
|
||||
help_data = {
|
||||
'prog_name': g.prog_name,
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import mmgen.config as g
|
|||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.util import *
|
||||
from mmgen.crypto import *
|
||||
|
||||
help_data = {
|
||||
'prog_name': g.prog_name,
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ def print_version_info():
|
|||
Copyright (C) {g.Cdates} by {g.author} {g.email}.
|
||||
""".format(g=g).strip()
|
||||
|
||||
def check_incompatible_opts(opts,incompat_list):
|
||||
def warn_incompatible_opts(opts,incompat_list):
|
||||
bad = [k for k in opts.keys() if k in incompat_list]
|
||||
if len(bad) > 1:
|
||||
msg("Mutually exclusive options: %s" % " ".join(
|
||||
|
|
@ -55,7 +55,7 @@ def parse_opts(argv,help_data):
|
|||
('export_incog','export_incog_hex','export_incog_hidden','export_mnemonic',
|
||||
'export_seed'),
|
||||
('quiet','verbose')
|
||||
): check_incompatible_opts(opts,l)
|
||||
): warn_incompatible_opts(opts,l)
|
||||
|
||||
# check_opts() doesn't touch opts[]
|
||||
if not check_opts(opts,long_opts): sys.exit(1)
|
||||
|
|
|
|||
394
mmgen/crypto.py
Executable file
394
mmgen/crypto.py
Executable file
|
|
@ -0,0 +1,394 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
crypto.py: Cryptographic and related routines for the mmgen-tool utility
|
||||
"""
|
||||
|
||||
import sys
|
||||
from binascii import hexlify
|
||||
from hashlib import sha256
|
||||
|
||||
import mmgen.config as g
|
||||
from mmgen.util import *
|
||||
from mmgen.term import get_char
|
||||
|
||||
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...")
|
||||
chk1 = make_chksum_8(key)
|
||||
if key_id:
|
||||
if not compare_checksums(chk1, "of key", key_id, "in header"):
|
||||
msg("Incorrect passphrase")
|
||||
return False
|
||||
|
||||
dec_seed = decrypt_data(enc_seed, key, iv=1, what="seed")
|
||||
|
||||
chk2 = make_chksum_8(dec_seed)
|
||||
|
||||
if seed_id:
|
||||
if compare_checksums(chk2,"of decrypted seed",seed_id,"in header"):
|
||||
qmsg("Passphrase is OK")
|
||||
else:
|
||||
if not g.debug:
|
||||
msg_r("Checking key ID...")
|
||||
if compare_checksums(chk1, "of key", key_id, "in header"):
|
||||
msg("Key ID is correct but decryption of seed failed")
|
||||
else:
|
||||
msg("Incorrect passphrase")
|
||||
|
||||
return False
|
||||
# else:
|
||||
# qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
|
||||
|
||||
if g.debug: print "Decrypted seed: %s" % hexlify(dec_seed)
|
||||
|
||||
return dec_seed
|
||||
|
||||
|
||||
def encrypt_data(data, key, iv=1, what="data", verify=True):
|
||||
"""
|
||||
Encrypt arbitrary data using AES256 in counter mode
|
||||
"""
|
||||
|
||||
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import Counter
|
||||
|
||||
vmsg("Encrypting %s" % what)
|
||||
|
||||
c = AES.new(key, AES.MODE_CTR,
|
||||
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
|
||||
enc_data = c.encrypt(data)
|
||||
|
||||
if verify:
|
||||
vmsg_r("Performing a test decryption of the %s..." % what)
|
||||
|
||||
c = AES.new(key, AES.MODE_CTR,
|
||||
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
|
||||
dec_data = c.decrypt(enc_data)
|
||||
|
||||
if dec_data == data: vmsg("done\n")
|
||||
else:
|
||||
msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what))
|
||||
sys.exit(2)
|
||||
|
||||
return enc_data
|
||||
|
||||
|
||||
def decrypt_data(enc_data, key, iv=1, what="data"):
|
||||
|
||||
vmsg("Decrypting %s with key..." % what)
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import Counter
|
||||
|
||||
c = AES.new(key, AES.MODE_CTR,
|
||||
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
|
||||
|
||||
return c.decrypt(enc_data)
|
||||
|
||||
|
||||
def scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
|
||||
|
||||
# Buflen arg is for brainwallets only, which use this function to generate
|
||||
# the seed directly.
|
||||
|
||||
N,r,p = get_hash_params(hash_preset)
|
||||
|
||||
import scrypt
|
||||
return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
|
||||
|
||||
|
||||
def make_key(passwd, salt, hash_preset, what="key"):
|
||||
|
||||
vmsg_r("Generating %s. Please wait..." % what)
|
||||
key = scrypt_hash_passphrase(passwd, salt, hash_preset)
|
||||
vmsg("done")
|
||||
if g.debug: print "Key: %s" % hexlify(key)
|
||||
return key
|
||||
|
||||
|
||||
def get_random_data_from_user(uchars):
|
||||
|
||||
if g.quiet: msg("Enter %s random symbols" % uchars)
|
||||
else: msg(cmessages['usr_rand_notice'] % uchars)
|
||||
|
||||
prompt = "You may begin typing. %s symbols left: "
|
||||
msg_r(prompt % uchars)
|
||||
|
||||
import time
|
||||
# time.clock() always returns zero, so we'll use time.time()
|
||||
saved_time = time.time()
|
||||
|
||||
key_data,time_data = "",[]
|
||||
|
||||
for i in range(uchars):
|
||||
key_data += get_char(immed_chars="ALL")
|
||||
msg_r("\r" + prompt % (uchars - i - 1))
|
||||
now = time.time()
|
||||
time_data.append(now - saved_time)
|
||||
saved_time = now
|
||||
|
||||
if g.quiet: msg_r("\r")
|
||||
else: msg_r("\rThank you. That's enough.%s\n\n" % (" "*18))
|
||||
|
||||
fmt_time_data = ["{:.22f}".format(i) for i in time_data]
|
||||
|
||||
if g.debug:
|
||||
msg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
|
||||
(key_data,"\n".join(fmt_time_data)))
|
||||
|
||||
prompt = "User random data successfully acquired. Press ENTER to continue"
|
||||
prompt_and_get_char(prompt,"",enter_ok=True)
|
||||
|
||||
return key_data+"".join(fmt_time_data)
|
||||
|
||||
|
||||
def get_random(length,opts):
|
||||
from Crypto import Random
|
||||
os_rand = Random.new().read(length)
|
||||
if 'usr_randchars' in opts and opts['usr_randchars'] not in (0,-1):
|
||||
kwhat = "a key from random data with "
|
||||
if not g.user_entropy:
|
||||
g.user_entropy = sha256(
|
||||
get_random_data_from_user(opts['usr_randchars'])).digest()
|
||||
kwhat += "user entropy"
|
||||
else:
|
||||
kwhat += "saved user entropy"
|
||||
key = make_key(g.user_entropy, "", '2', what=kwhat)
|
||||
return encrypt_data(os_rand,key,what="random data",verify=False)
|
||||
else:
|
||||
return os_rand
|
||||
|
||||
|
||||
def get_seed_from_wallet(
|
||||
infile,
|
||||
opts,
|
||||
prompt="Enter {} wallet passphrase: ".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)
|
||||
|
||||
passwd = get_mmgen_passphrase(prompt,opts)
|
||||
|
||||
key = make_key(passwd, salt, hash_preset)
|
||||
|
||||
return decrypt_seed(enc_seed, key, metadata[0], metadata[1])
|
||||
|
||||
|
||||
def get_seed_from_incog_wallet(
|
||||
infile,
|
||||
opts,
|
||||
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
|
||||
silent=False,
|
||||
hex_input=False
|
||||
):
|
||||
|
||||
what = "incognito wallet data"
|
||||
|
||||
if "from_incog_hidden" in opts:
|
||||
d = get_hidden_incog_data(opts)
|
||||
else:
|
||||
d = get_data_from_file(infile,what)
|
||||
if hex_input:
|
||||
try:
|
||||
d = unhexlify("".join(d.split()).strip())
|
||||
except:
|
||||
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]
|
||||
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]))
|
||||
)
|
||||
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)))
|
||||
qmsg("Check the applicable value against your records.")
|
||||
vmsg(cmessages['incog_iv_id_hidden' if "from_incog_hidden" in opts
|
||||
else 'incog_iv_id'])
|
||||
|
||||
passwd = get_mmgen_passphrase(prompt,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)
|
||||
|
||||
# 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)
|
||||
|
||||
salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
|
||||
|
||||
key = make_key(passwd, salt, hp, "main key")
|
||||
vmsg("Key ID: %s" % make_chksum_8(key))
|
||||
|
||||
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'])
|
||||
|
||||
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("Enter mmgen passphrase: ",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):
|
||||
|
||||
ext = get_extension(infile)
|
||||
|
||||
if ext == g.mn_ext: source = "mnemonic"
|
||||
elif ext == g.brain_ext: source = "brainwallet"
|
||||
elif ext == g.seed_ext: source = "seed"
|
||||
elif ext == g.wallet_ext: source = "wallet"
|
||||
elif ext == g.incog_ext: source = "incognito wallet"
|
||||
elif ext == g.incog_hex_ext: source = "incognito wallet"
|
||||
elif 'from_mnemonic' in opts: source = "mnemonic"
|
||||
elif 'from_brain' in opts: source = "brainwallet"
|
||||
elif 'from_seed' in opts: source = "seed"
|
||||
elif 'from_incog' in opts: source = "incognito wallet"
|
||||
else:
|
||||
if infile: msg(
|
||||
"Invalid file extension for file: %s\nValid extensions: '.%s'" %
|
||||
(infile, "', '.".join(g.seedfile_exts)))
|
||||
else: msg("No seed source type specified and no file supplied")
|
||||
sys.exit(2)
|
||||
|
||||
if source == "mnemonic":
|
||||
prompt = "Enter mnemonic: "
|
||||
words = get_words(infile,"mnemonic data",prompt,opts)
|
||||
wl = get_default_wordlist()
|
||||
from mmgen.mnemonic import get_seed_from_mnemonic
|
||||
seed = get_seed_from_mnemonic(words,wl)
|
||||
elif source == "brainwallet":
|
||||
if 'from_brain' not in opts:
|
||||
msg("'--from-brain' parameters must be specified for brainwallet file")
|
||||
sys.exit(2)
|
||||
prompt = "Enter brainwallet passphrase: "
|
||||
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
|
||||
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
|
||||
seed = get_seed_from_incog_wallet(infile, opts, silent=silent, hex_input=h)
|
||||
|
||||
|
||||
if infile and not seed and (
|
||||
source == "seed" or source == "mnemonic" or source == "incognito wallet"):
|
||||
msg("Invalid %s file '%s'" % (source,infile))
|
||||
sys.exit(2)
|
||||
|
||||
if g.debug: print "Seed: %s" % hexlify(seed)
|
||||
|
||||
return seed
|
||||
|
||||
|
||||
# Repeat if entered data is invalid
|
||||
def get_seed_retry(infile,opts):
|
||||
silent = False
|
||||
while True:
|
||||
seed = get_seed(infile,opts,silent=silent)
|
||||
silent = True
|
||||
if seed: return seed
|
||||
|
||||
|
||||
def _get_seed_from_brain_passphrase(words,opts):
|
||||
bp = " ".join(words)
|
||||
if g.debug: print "Sanitized brain passphrase: %s" % bp
|
||||
seed_len,hash_preset = get_from_brain_opt_params(opts)
|
||||
if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset)
|
||||
vmsg_r("Hashing brainwallet data. Please wait...")
|
||||
# Use buflen arg of scrypt.hash() to get seed of desired length
|
||||
seed = scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
|
||||
vmsg("Done")
|
||||
return seed
|
||||
|
||||
|
||||
# Vars for mmgen_*crypt functions only
|
||||
salt_len,sha256_len,nonce_len = 32,32,32
|
||||
|
||||
def mmgen_encrypt(data,hash_preset,opts):
|
||||
salt,iv,nonce = get_random(salt_len,opts),\
|
||||
get_random(g.aesctr_iv_len,opts), get_random(nonce_len,opts)
|
||||
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
|
||||
qmsg("Using %s hash preset of '%s'" % (m,hp))
|
||||
passwd = get_new_passphrase("passphrase",{})
|
||||
key = make_key(passwd, salt, hp)
|
||||
enc_d = encrypt_data(sha256(nonce+data).digest() + nonce + data, key,
|
||||
int(hexlify(iv),16))
|
||||
return salt+iv+enc_d
|
||||
|
||||
|
||||
def mmgen_decrypt(data,hash_preset,opts):
|
||||
dstart = salt_len + g.aesctr_iv_len
|
||||
salt,iv,enc_d = data[:salt_len],data[salt_len:dstart],data[dstart:]
|
||||
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
|
||||
qmsg("Using %s hash preset of '%s'" % (m,hp))
|
||||
passwd = get_mmgen_passphrase("Enter passphrase: ",{})
|
||||
key = make_key(passwd, salt, hp)
|
||||
dec_d = decrypt_data(enc_d, key, int(hexlify(iv),16))
|
||||
if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
|
||||
return dec_d[sha256_len+nonce_len:]
|
||||
else:
|
||||
msg("Incorrect passphrase or hash preset")
|
||||
return False
|
||||
|
|
@ -20,7 +20,8 @@ license.py: Show the license
|
|||
"""
|
||||
|
||||
import sys
|
||||
from mmgen.util import msg, msg_r, get_char
|
||||
from mmgen.util import msg, msg_r
|
||||
from mmgen.term import get_char
|
||||
import mmgen.config as g
|
||||
|
||||
gpl = {
|
||||
|
|
@ -595,7 +596,7 @@ def do_license_msg(immed=False):
|
|||
while True:
|
||||
reply = get_char(prompt, immed_chars="wc" if immed else "")
|
||||
if reply == 'w':
|
||||
from mmgen.util import do_pager
|
||||
from mmgen.term import do_pager
|
||||
do_pager(gpl['conditions'])
|
||||
elif reply == 'c':
|
||||
msg(""); break
|
||||
|
|
|
|||
|
|
@ -20,9 +20,7 @@ term.py: Terminal-handling routines for the mmgen suite
|
|||
"""
|
||||
|
||||
import sys, os, struct
|
||||
|
||||
def msg(s): sys.stderr.write(s + "\n")
|
||||
def msg_r(s): sys.stderr.write(s)
|
||||
from mmgen.util import msg, msg_r
|
||||
|
||||
def _kb_hold_protect_unix():
|
||||
|
||||
|
|
@ -122,7 +120,6 @@ def _get_terminal_size_linux():
|
|||
def ioctl_GWINSZ(fd):
|
||||
try:
|
||||
import fcntl
|
||||
import termios
|
||||
cr = struct.unpack('hh', fcntl.ioctl(fd, termios.TIOCGWINSZ, '1234'))
|
||||
return cr
|
||||
except:
|
||||
|
|
@ -165,18 +162,23 @@ def _get_terminal_size_mswin():
|
|||
except:
|
||||
return 80,25
|
||||
|
||||
def mswin_dummy_flush(fd,termconst): pass
|
||||
|
||||
try:
|
||||
import tty, termios
|
||||
from select import select
|
||||
get_char = _get_keypress_unix
|
||||
kb_hold_protect = _kb_hold_protect_unix
|
||||
get_terminal_size = _get_terminal_size_linux
|
||||
myflush = termios.tcflush
|
||||
# call: myflush(sys.stdin, termios.TCIOFLUSH)
|
||||
except:
|
||||
try:
|
||||
import msvcrt, time
|
||||
get_char = _get_keypress_mswin
|
||||
kb_hold_protect = _kb_hold_protect_mswin
|
||||
get_terminal_size = _get_terminal_size_mswin
|
||||
myflush = mswin_dummy_flush
|
||||
except:
|
||||
if not sys.platform.startswith("linux") \
|
||||
and not sys.platform.startswith("win"):
|
||||
|
|
@ -186,5 +188,42 @@ except:
|
|||
msg("Unable to set terminal mode")
|
||||
sys.exit(2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "columns: {}, rows: {}".format(*get_terminal_size())
|
||||
|
||||
def do_pager(text):
|
||||
|
||||
pagers = ["less","more"]
|
||||
shell = False
|
||||
|
||||
from os import environ
|
||||
|
||||
# Hack for MS Windows command line (i.e. non CygWin) environment
|
||||
# When 'shell' is true, Windows aborts the calling program if executable
|
||||
# not found.
|
||||
# When 'shell' is false, an exception is raised, invoking the fallback
|
||||
# 'print' instead of the pager.
|
||||
# We risk assuming that "more" will always be available on a stock
|
||||
# Windows installation.
|
||||
if sys.platform.startswith("win") and 'HOME' not in environ:
|
||||
shell = True
|
||||
pagers = ["more"]
|
||||
|
||||
if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
|
||||
pagers = [environ['PAGER']] + pagers
|
||||
|
||||
for pager in pagers:
|
||||
end = "" if pager == "less" else "\n(end of text)\n"
|
||||
try:
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
p = Popen([pager], stdin=PIPE, shell=shell)
|
||||
except: pass
|
||||
else:
|
||||
try:
|
||||
p.communicate(text+end+"\n")
|
||||
except KeyboardInterrupt:
|
||||
# Has no effect. Why?
|
||||
if pager != "less":
|
||||
msg("\n(User interrupt)\n")
|
||||
finally:
|
||||
msg_r("\r")
|
||||
break
|
||||
else: print text+end
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import mmgen.bitcoin as bitcoin
|
|||
import binascii as ba
|
||||
|
||||
import mmgen.config as g
|
||||
from mmgen.crypto import *
|
||||
from mmgen.util import *
|
||||
from mmgen.tx import *
|
||||
|
||||
|
|
@ -55,7 +56,8 @@ commands = {
|
|||
"mn_printlist": ['wordlist [str="electrum"]'],
|
||||
"id8": ['<infile> [str]'],
|
||||
"id6": ['<infile> [str]'],
|
||||
"listaddresses": ['minconf [int=1]', 'showempty [bool=False]'],
|
||||
"str2id6": ['<string (spaces are ignored)> [str]'],
|
||||
"listaddresses":['minconf [int=1]', 'showempty [bool=False]'],
|
||||
"getbalance": ['minconf [int=1]'],
|
||||
"viewtx": ['<MMGen tx file> [str]'],
|
||||
"check_addrfile": ['<MMGen addr file> [str]'],
|
||||
|
|
@ -119,8 +121,9 @@ command_help = """
|
|||
{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
|
||||
id6 - generate 6-character {pnm} ID checksum for file (or stdin)
|
||||
id8 - generate 8-character {pnm} ID checksum for file (or stdin)
|
||||
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
|
||||
|
||||
Mnemonic operations (choose "electrum" (default), "tirosh" or "all"
|
||||
wordlists):
|
||||
|
|
@ -301,6 +304,7 @@ def mn_printlist(wordlist="electrum"):
|
|||
|
||||
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):
|
||||
|
|
@ -410,48 +414,31 @@ def wif2hex(wif,compressed=False):
|
|||
def hex2wif(hexpriv,compressed=False):
|
||||
print bitcoin.hextowif(hexpriv,compressed)
|
||||
|
||||
salt_len,sha256_len,nonce_len = 32,32,32
|
||||
|
||||
def encrypt(infile,outfile="",hash_preset=''):
|
||||
d = get_data_from_file(infile,"data for encryption")
|
||||
salt,iv,nonce = get_random(salt_len,opts),\
|
||||
get_random(g.aesctr_iv_len,opts), get_random(nonce_len,opts)
|
||||
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
|
||||
qmsg("Using %s hash preset of '%s'" % (m,hp))
|
||||
passwd = get_new_passphrase("passphrase",{})
|
||||
key = make_key(passwd, salt, hp)
|
||||
from hashlib import sha256
|
||||
enc_d = encrypt_data(sha256(nonce+d).digest() + nonce + d, key,
|
||||
int(ba.hexlify(iv),16))
|
||||
if outfile == '-': sys.stdout.write(salt+iv+enc_d)
|
||||
data = get_data_from_file(infile,"data for encryption")
|
||||
enc_d = mmgen_encrypt(data,hash_preset,opts)
|
||||
if outfile == '-':
|
||||
write_to_stdout(enc_d,"encrypted data",confirm=True)
|
||||
else:
|
||||
if not outfile:
|
||||
outfile = os.path.basename(infile) + "." + g.mmenc_ext
|
||||
write_to_file(outfile, salt+iv+enc_d, opts,"encrypted data",True,True)
|
||||
write_to_file(outfile, enc_d, opts,"encrypted data",True,True)
|
||||
|
||||
|
||||
def decrypt(infile,outfile="",hash_preset=''):
|
||||
d = get_data_from_file(infile,"encrypted data")
|
||||
dstart = salt_len + g.aesctr_iv_len
|
||||
salt,iv,enc_d = d[:salt_len],d[salt_len:dstart],d[dstart:]
|
||||
hp,m = (hash_preset,"user-requested") if hash_preset else ('3',"default")
|
||||
qmsg("Using %s hash preset of '%s'" % (m,hp))
|
||||
passwd = get_mmgen_passphrase("Enter passphrase: ",{})
|
||||
key = make_key(passwd, salt, hp)
|
||||
dec_d = decrypt_data(enc_d, key, int(ba.hexlify(iv),16))
|
||||
from hashlib import sha256
|
||||
if dec_d[:sha256_len] == sha256(dec_d[sha256_len:]).digest():
|
||||
out = dec_d[sha256_len+nonce_len:]
|
||||
if outfile == '-': sys.stdout.write(out)
|
||||
else:
|
||||
if not outfile:
|
||||
outfile = os.path.basename(infile)
|
||||
if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext:
|
||||
outfile = outfile[:-len(g.mmenc_ext)-1]
|
||||
else:
|
||||
outfile = outfile + ".dec"
|
||||
write_to_file(outfile, out, opts,"decrypted data",True,True)
|
||||
enc_d = get_data_from_file(infile,"encrypted data")
|
||||
dec_d = mmgen_decrypt(enc_d,hash_preset,opts)
|
||||
if outfile == '-':
|
||||
write_to_stdout(dec_d,"decrypted data",confirm=True)
|
||||
else:
|
||||
msg("Incorrect passphrase or hash preset")
|
||||
if not outfile:
|
||||
outfile = os.path.basename(infile)
|
||||
if outfile[-len(g.mmenc_ext)-1:] == "."+g.mmenc_ext:
|
||||
outfile = outfile[:-len(g.mmenc_ext)-1]
|
||||
else:
|
||||
outfile = outfile + ".dec"
|
||||
write_to_file(outfile, dec_d, opts,"decrypted data",True,True)
|
||||
|
||||
|
||||
def find_incog_data(filename,iv_id,keep_searching=False):
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ 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)",
|
||||
|
|
@ -538,7 +540,7 @@ def check_addr_data_hash(seed_id,addr_data):
|
|||
|
||||
def parse_addrs_file(f):
|
||||
|
||||
lines = get_lines_from_file(f,"address data",remove_comments=True)
|
||||
lines = get_lines_from_file(f,"address data",trim_comments=True)
|
||||
|
||||
try:
|
||||
seed_id,obrace = lines[0].split()
|
||||
|
|
|
|||
760
mmgen/util.py
760
mmgen/util.py
|
|
@ -16,7 +16,7 @@
|
|||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
"""
|
||||
util.py: Shared routines for the mmgen suite
|
||||
util.py: Low-level routines imported by other modules for the MMGen suite
|
||||
"""
|
||||
|
||||
import sys
|
||||
|
|
@ -24,8 +24,6 @@ from hashlib import sha256
|
|||
from binascii import hexlify,unhexlify
|
||||
|
||||
import mmgen.config as g
|
||||
from mmgen.bitcoin import b58decode_pad,b58encode_pad
|
||||
from mmgen.term import *
|
||||
|
||||
def msg(s): sys.stderr.write(s + "\n")
|
||||
def msg_r(s): sys.stderr.write(s)
|
||||
|
|
@ -42,97 +40,6 @@ def vmsg(s):
|
|||
def vmsg_r(s):
|
||||
if g.verbose: sys.stderr.write(s)
|
||||
|
||||
def bail(): sys.exit(9)
|
||||
|
||||
def get_extension(f):
|
||||
import os
|
||||
return os.path.splitext(f)[1][1:]
|
||||
|
||||
def get_random_data_from_user(uchars):
|
||||
|
||||
if g.quiet: msg("Enter %s random symbols" % uchars)
|
||||
else: msg(cmessages['usr_rand_notice'] % uchars)
|
||||
|
||||
prompt = "You may begin typing. %s symbols left: "
|
||||
msg_r(prompt % uchars)
|
||||
|
||||
import time
|
||||
# time.clock() always returns zero, so we'll use time.time()
|
||||
saved_time = time.time()
|
||||
|
||||
key_data,time_data = "",[]
|
||||
|
||||
for i in range(uchars):
|
||||
key_data += get_char(immed_chars="ALL")
|
||||
msg_r("\r" + prompt % (uchars - i - 1))
|
||||
now = time.time()
|
||||
time_data.append(now - saved_time)
|
||||
saved_time = now
|
||||
|
||||
if g.quiet: msg_r("\r")
|
||||
else: msg_r("\rThank you. That's enough.%s\n\n" % (" "*18))
|
||||
|
||||
fmt_time_data = ["{:.22f}".format(i) for i in time_data]
|
||||
|
||||
if g.debug:
|
||||
msg("\nUser input:\n%s\nKeystroke time intervals:\n%s\n" %
|
||||
(key_data,"\n".join(fmt_time_data)))
|
||||
|
||||
prompt = "User random data successfully acquired. Press ENTER to continue"
|
||||
prompt_and_get_char(prompt,"",enter_ok=True)
|
||||
|
||||
return key_data+"".join(fmt_time_data)
|
||||
|
||||
|
||||
def get_random(length,opts):
|
||||
from Crypto import Random
|
||||
os_rand = Random.new().read(length)
|
||||
if 'usr_randchars' in opts and opts['usr_randchars'] not in (0,-1):
|
||||
kwhat = "a key from random data with "
|
||||
if not g.user_entropy:
|
||||
g.user_entropy = sha256(
|
||||
get_random_data_from_user(opts['usr_randchars'])).digest()
|
||||
kwhat += "user entropy"
|
||||
else:
|
||||
kwhat += "saved user entropy"
|
||||
key = make_key(g.user_entropy, "", '2', what=kwhat)
|
||||
return encrypt_data(os_rand,key,what="random data",verify=False)
|
||||
else:
|
||||
return os_rand
|
||||
|
||||
def my_raw_input(prompt,echo=True):
|
||||
try:
|
||||
if echo:
|
||||
reply = raw_input(prompt)
|
||||
else:
|
||||
from getpass import getpass
|
||||
reply = getpass(prompt)
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
kb_hold_protect()
|
||||
return reply
|
||||
|
||||
|
||||
def _get_hash_params(hash_preset):
|
||||
if hash_preset in g.hash_presets:
|
||||
return g.hash_presets[hash_preset] # N,p,r,buflen
|
||||
else: # Shouldn't be here
|
||||
msg("%s: invalid 'hash_preset' value" % hash_preset)
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def show_hash_presets():
|
||||
fs = " {:<7} {:<6} {:<3} {}"
|
||||
msg("Available parameters for scrypt.hash():")
|
||||
msg(fs.format("Preset","N","r","p"))
|
||||
for i in sorted(g.hash_presets.keys()):
|
||||
msg(fs.format("'%s'" % i, *g.hash_presets[i]))
|
||||
msg("N = memory usage (power of two), p = iterations (rounds)")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
cmessages = {
|
||||
'null': "",
|
||||
'incog_iv_id': """
|
||||
|
|
@ -189,71 +96,113 @@ just hit ENTER twice.
|
|||
""".strip()
|
||||
}
|
||||
|
||||
|
||||
def confirm_or_exit(message, question, expect="YES"):
|
||||
|
||||
vmsg("")
|
||||
|
||||
m = message.strip()
|
||||
if m: msg(m)
|
||||
|
||||
conf_msg = "Type uppercase '%s' to confirm: " % expect
|
||||
|
||||
p = question+" "+conf_msg if question[0].isupper() else \
|
||||
"Are you sure you want to %s?\n%s" % (question,conf_msg)
|
||||
|
||||
if my_raw_input(p).strip() != expect:
|
||||
msg("Exiting at user request")
|
||||
sys.exit(2)
|
||||
|
||||
vmsg("")
|
||||
|
||||
|
||||
def user_confirm(prompt,default_yes=False,verbose=False):
|
||||
|
||||
q = "(Y/n)" if default_yes else "(y/N)"
|
||||
|
||||
while True:
|
||||
reply = get_char("%s %s: " % (prompt, q)).strip("\n\r")
|
||||
|
||||
if not reply:
|
||||
if default_yes: msg(""); return True
|
||||
else: msg(""); return False
|
||||
elif reply in 'yY': msg(""); return True
|
||||
elif reply in 'nN': msg(""); return False
|
||||
else:
|
||||
if verbose: msg("\nInvalid reply")
|
||||
else: msg_r("\r")
|
||||
|
||||
|
||||
def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
|
||||
|
||||
while True:
|
||||
reply = get_char("%s: " % prompt).strip("\n\r")
|
||||
|
||||
if reply in chars or (enter_ok and not reply):
|
||||
msg("")
|
||||
return reply
|
||||
|
||||
if verbose: msg("\nInvalid reply")
|
||||
else: msg_r("\r")
|
||||
|
||||
def get_extension(f):
|
||||
import os
|
||||
return os.path.splitext(f)[1][1:]
|
||||
|
||||
def make_chksum_N(s,n,sep=False):
|
||||
if n%4 or not (4 <= n <= 64): return False
|
||||
s = sha256(sha256(s).digest()).hexdigest().upper()
|
||||
sep = " " if sep else ""
|
||||
return sep.join([s[i*4:i*4+4] for i in range(n/4)])
|
||||
|
||||
def make_chksum_8(s,sep=False):
|
||||
s = sha256(sha256(s).digest()).hexdigest()[:8].upper()
|
||||
return "{} {}".format(s[:4],s[4:]) if sep else s
|
||||
def make_chksum_6(s): return sha256(s).hexdigest()[:6]
|
||||
def make_iv_chksum(s): return sha256(s).hexdigest()[:8].upper()
|
||||
|
||||
def make_chksum_6(s):
|
||||
return sha256(s).hexdigest()[:6]
|
||||
def splitN(s,n,sep=None): # always return an n-element list
|
||||
ret = s.split(sep,n-1)
|
||||
return ret + ["" for i in range(n-len(ret))]
|
||||
def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
|
||||
def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
|
||||
|
||||
def make_iv_chksum(s):
|
||||
return sha256(s).hexdigest()[:8].upper()
|
||||
def col4(s):
|
||||
nondiv = 1 if len(s) % 4 else 0
|
||||
return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)])
|
||||
|
||||
def make_timestamp():
|
||||
import time
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
|
||||
def make_timestr():
|
||||
import time
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
return "{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}".format(*tv)
|
||||
def secs_to_hms(secs):
|
||||
return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
|
||||
|
||||
def _is_hex(s):
|
||||
try: int(s,16)
|
||||
except: return False
|
||||
else: return True
|
||||
|
||||
def match_ext(addr,ext):
|
||||
return addr.split(".")[-1] == ext
|
||||
|
||||
def get_from_brain_opt_params(opts):
|
||||
l,p = opts['from_brain'].split(",")
|
||||
return(int(l),p)
|
||||
|
||||
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)) +
|
||||
hexlify(data[i*gw:i*gw+gw]) +
|
||||
(" " if (i+1) % cols else "\n")
|
||||
for i in range(len(data)/gw + r)
|
||||
]
|
||||
).rstrip()
|
||||
|
||||
def decode_pretty_hexdump(data):
|
||||
import re
|
||||
lines = [re.sub('^\d+:\s+','',l) for l in data.split("\n")]
|
||||
return unhexlify("".join(("".join(lines).split())))
|
||||
|
||||
def get_hash_params(hash_preset):
|
||||
if hash_preset in g.hash_presets:
|
||||
return g.hash_presets[hash_preset] # N,p,r,buflen
|
||||
else: # Shouldn't be here
|
||||
msg("%s: invalid 'hash_preset' value" % hash_preset)
|
||||
sys.exit(3)
|
||||
|
||||
def show_hash_presets():
|
||||
fs = " {:<7} {:<6} {:<3} {}"
|
||||
msg("Available parameters for scrypt.hash():")
|
||||
msg(fs.format("Preset","N","r","p"))
|
||||
for i in sorted(g.hash_presets.keys()):
|
||||
msg(fs.format("'%s'" % i, *g.hash_presets[i]))
|
||||
msg("N = memory usage (power of two), p = iterations (rounds)")
|
||||
sys.exit(0)
|
||||
|
||||
def compare_checksums(chksum1, desc1, chksum2, desc2):
|
||||
|
||||
if chksum1.lower() == chksum2.lower():
|
||||
vmsg("OK (%s)" % chksum1.upper())
|
||||
return True
|
||||
else:
|
||||
if g.debug:
|
||||
print \
|
||||
"ERROR!\nComputed checksum %s (%s) doesn't match checksum %s (%s)" \
|
||||
% (desc1,chksum1,desc2,chksum2)
|
||||
return False
|
||||
|
||||
def get_default_wordlist():
|
||||
|
||||
wl_id = g.default_wl
|
||||
if wl_id == "electrum": from mmgen.mn_electrum import electrum_words as wl
|
||||
elif wl_id == "tirosh": from mmgen.mn_tirosh import tirosh_words as wl
|
||||
return wl.strip().split("\n")
|
||||
|
||||
def open_file_or_exit(filename,mode):
|
||||
try:
|
||||
f = open(filename, mode)
|
||||
except:
|
||||
what = "reading" if mode == 'r' else "writing"
|
||||
msg("Unable to open file '%s' for %s" % (filename,what))
|
||||
sys.exit(2)
|
||||
return f
|
||||
|
||||
|
||||
def check_file_type_and_access(fname,ftype):
|
||||
|
|
@ -287,6 +236,7 @@ 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)
|
||||
|
|
@ -301,6 +251,12 @@ def _validate_addr_num(n):
|
|||
return n
|
||||
|
||||
|
||||
def make_full_path(outdir,outfile):
|
||||
import os
|
||||
return os.path.normpath(os.sep.join([outdir, os.path.basename(outfile)]))
|
||||
# os.path.join() doesn't work?
|
||||
|
||||
|
||||
def parse_address_list(arg,sep=","):
|
||||
|
||||
ret = []
|
||||
|
|
@ -354,65 +310,23 @@ def get_new_passphrase(what, opts):
|
|||
return pw
|
||||
|
||||
|
||||
def _scrypt_hash_passphrase(passwd, salt, hash_preset, buflen=32):
|
||||
def confirm_or_exit(message, question, expect="YES"):
|
||||
|
||||
# Buflen arg is for brainwallets only, which use this function to generate
|
||||
# the seed directly.
|
||||
vmsg("")
|
||||
|
||||
N,r,p = _get_hash_params(hash_preset)
|
||||
m = message.strip()
|
||||
if m: msg(m)
|
||||
|
||||
import scrypt
|
||||
return scrypt.hash(passwd, salt, 2**N, r, p, buflen=buflen)
|
||||
conf_msg = "Type uppercase '%s' to confirm: " % expect
|
||||
|
||||
p = question+" "+conf_msg if question[0].isupper() else \
|
||||
"Are you sure you want to %s?\n%s" % (question,conf_msg)
|
||||
|
||||
def get_from_brain_opt_params(opts):
|
||||
l,p = opts['from_brain'].split(",")
|
||||
return(int(l),p)
|
||||
if my_raw_input(p).strip() != expect:
|
||||
msg("Exiting at user request")
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def _get_seed_from_brain_passphrase(words,opts):
|
||||
bp = " ".join(words)
|
||||
if g.debug: print "Sanitized brain passphrase: %s" % bp
|
||||
seed_len,hash_preset = get_from_brain_opt_params(opts)
|
||||
if g.debug: print "Brainwallet l = %s, p = %s" % (seed_len,hash_preset)
|
||||
vmsg_r("Hashing brainwallet data. Please wait...")
|
||||
# Use buflen arg of scrypt.hash() to get seed of desired length
|
||||
seed = _scrypt_hash_passphrase(bp, "", hash_preset, buflen=seed_len/8)
|
||||
vmsg("Done")
|
||||
return seed
|
||||
|
||||
|
||||
def encrypt_seed(seed, key):
|
||||
return encrypt_data(seed, key, iv=1, what="seed")
|
||||
|
||||
def encrypt_data(data, key, iv=1, what="data", verify=True):
|
||||
"""
|
||||
Encrypt arbitrary data using AES256 in counter mode
|
||||
"""
|
||||
|
||||
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import Counter
|
||||
|
||||
vmsg("Encrypting %s" % what)
|
||||
|
||||
c = AES.new(key, AES.MODE_CTR,
|
||||
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
|
||||
enc_data = c.encrypt(data)
|
||||
|
||||
if verify:
|
||||
vmsg_r("Performing a test decryption of the %s..." % what)
|
||||
|
||||
c = AES.new(key, AES.MODE_CTR,
|
||||
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
|
||||
dec_data = c.decrypt(enc_data)
|
||||
|
||||
if dec_data == data: vmsg("done\n")
|
||||
else:
|
||||
msg("ERROR.\nDecrypted %s doesn't match original %s" % (what,what))
|
||||
sys.exit(2)
|
||||
|
||||
return enc_data
|
||||
vmsg("")
|
||||
|
||||
|
||||
def write_to_stdout(data, what, confirm=True):
|
||||
|
|
@ -422,36 +336,12 @@ 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'" % of)
|
||||
msg("Redirecting output to file '%s'" % os.path.relpath(of))
|
||||
except:
|
||||
msg("Redirecting output to file")
|
||||
sys.stdout.write(data)
|
||||
|
||||
|
||||
def get_default_wordlist():
|
||||
|
||||
wl_id = g.default_wl
|
||||
if wl_id == "electrum": from mmgen.mn_electrum import electrum_words as wl
|
||||
elif wl_id == "tirosh": from mmgen.mn_tirosh import tirosh_words as wl
|
||||
return wl.strip().split("\n")
|
||||
|
||||
|
||||
def open_file_or_exit(filename,mode):
|
||||
try:
|
||||
f = open(filename, mode)
|
||||
except:
|
||||
what = "reading" if mode == 'r' else "writing"
|
||||
msg("Unable to open file '%s' for %s" % (filename,what))
|
||||
sys.exit(2)
|
||||
return f
|
||||
|
||||
|
||||
def make_full_path(outdir,outfile):
|
||||
import os
|
||||
return os.path.normpath(os.sep.join([outdir, os.path.basename(outfile)]))
|
||||
# os.path.join() doesn't work?
|
||||
|
||||
|
||||
def write_to_file(outfile,data,opts,what="data",confirm=False,verbose=False):
|
||||
|
||||
if 'outdir' in opts: outfile = make_full_path(opts['outdir'],outfile)
|
||||
|
|
@ -487,11 +377,12 @@ def export_to_file(outfile, data, opts, what="data"):
|
|||
write_to_file(outfile,data,opts,what,c,True)
|
||||
|
||||
|
||||
def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
|
||||
from mmgen.bitcoin import b58decode_pad,b58encode_pad
|
||||
|
||||
def display_control_data(label,metadata,hash_preset,salt,enc_seed):
|
||||
msg("WALLET DATA")
|
||||
fs = " {:18} {}"
|
||||
pw_empty = "yes" if metadata[3] == "E" else "no"
|
||||
from mmgen.bitcoin import b58encode_pad
|
||||
for i in (
|
||||
("Label:", label),
|
||||
("Seed ID:", metadata[0].upper()),
|
||||
|
|
@ -499,7 +390,7 @@ def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
|
|||
("Seed length:", "%s bits (%s bytes)" %
|
||||
(metadata[2],int(metadata[2])/8)),
|
||||
("Scrypt params:", "Preset '%s' (%s)" % (hash_preset,
|
||||
" ".join([str(i) for i in _get_hash_params(hash_preset)]))),
|
||||
" ".join([str(i) for i in get_hash_params(hash_preset)]))),
|
||||
("Passphrase empty?", pw_empty.capitalize()),
|
||||
("Timestamp:", "%s UTC" % metadata[4]),
|
||||
): msg(fs.format(*i))
|
||||
|
|
@ -515,31 +406,6 @@ def _display_control_data(label,metadata,hash_preset,salt,enc_seed):
|
|||
): msg(fs.format(*i))
|
||||
|
||||
|
||||
def splitN(s,n,sep=None): # always return an n-element list
|
||||
ret = s.split(sep,n-1)
|
||||
return ret + ["" for i in range(n-len(ret))]
|
||||
|
||||
def split2(s,sep=None): return splitN(s,2,sep) # always return a 2-element list
|
||||
def split3(s,sep=None): return splitN(s,3,sep) # always return a 3-element list
|
||||
|
||||
def col4(s):
|
||||
nondiv = 1 if len(s) % 4 else 0
|
||||
return " ".join([s[4*i:4*i+4] for i in range(len(s)/4 + nondiv)])
|
||||
|
||||
def make_timestamp():
|
||||
import time
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
return "{:04d}{:02d}{:02d}_{:02d}{:02d}{:02d}".format(*tv)
|
||||
|
||||
def make_timestr():
|
||||
import time
|
||||
tv = time.gmtime(time.time())[:6]
|
||||
return "{:04d}/{:02d}/{:02d} {:02d}:{:02d}:{:02d}".format(*tv)
|
||||
|
||||
def secs_to_hms(secs):
|
||||
return "{:02d}:{:02d}:{:02d}".format(secs/3600, (secs/60) % 60, secs % 60)
|
||||
|
||||
|
||||
def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
||||
|
||||
seed_id = make_chksum_8(seed)
|
||||
|
|
@ -555,7 +421,7 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
|||
lines = (
|
||||
label,
|
||||
"{} {} {} {} {}".format(*metadata),
|
||||
"{}: {} {} {}".format(hash_preset,*_get_hash_params(hash_preset)),
|
||||
"{}: {} {} {}".format(hash_preset,*get_hash_params(hash_preset)),
|
||||
"{} {}".format(make_chksum_6(sf), col4(sf)),
|
||||
"{} {}".format(make_chksum_6(esf), col4(esf))
|
||||
)
|
||||
|
|
@ -569,29 +435,9 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
|||
write_to_file(outfile,d,opts,"wallet",c,True)
|
||||
|
||||
if g.verbose:
|
||||
_display_control_data(label,metadata,hash_preset,salt,enc_seed)
|
||||
display_control_data(label,metadata,hash_preset,salt,enc_seed)
|
||||
|
||||
|
||||
def _compare_checksums(chksum1, desc1, chksum2, desc2):
|
||||
|
||||
if chksum1.lower() == chksum2.lower():
|
||||
vmsg("OK (%s)" % chksum1.upper())
|
||||
return True
|
||||
else:
|
||||
if g.debug:
|
||||
print \
|
||||
"ERROR!\nComputed checksum %s (%s) doesn't match checksum %s (%s)" \
|
||||
% (desc1,chksum1,desc2,chksum2)
|
||||
return False
|
||||
|
||||
def _is_hex(s):
|
||||
try: int(s,16)
|
||||
except: return False
|
||||
else: return True
|
||||
|
||||
def match_ext(addr,ext):
|
||||
return addr.split(".")[-1] == ext
|
||||
|
||||
def _check_mmseed_format(words):
|
||||
|
||||
valid = False
|
||||
|
|
@ -664,7 +510,7 @@ def get_data_from_wallet(infile,silent=False):
|
|||
hash_preset = hd[0][:-1]
|
||||
hash_params = [int(i) for i in hd[1:]]
|
||||
|
||||
if hash_params != _get_hash_params(hash_preset):
|
||||
if hash_params != get_hash_params(hash_preset):
|
||||
msg("Hash parameters '%s' don't match hash preset '%s'" %
|
||||
(" ".join(hash_params), hash_preset))
|
||||
sys.exit(9)
|
||||
|
|
@ -702,22 +548,29 @@ def _get_words_from_file(infile,what):
|
|||
return words
|
||||
|
||||
|
||||
def get_lines_from_file(infile,what="",remove_comments=False):
|
||||
def get_words(infile,what,prompt,opts):
|
||||
if infile:
|
||||
return _get_words_from_file(infile,what)
|
||||
else:
|
||||
return _get_words_from_user(prompt,opts)
|
||||
|
||||
def remove_comments(lines):
|
||||
import re
|
||||
# re.sub(pattern, repl, string, count=0, flags=0)
|
||||
ret = []
|
||||
for i in lines:
|
||||
i = re.sub('#.*','',i,1)
|
||||
i = re.sub('\s+$','',i)
|
||||
if i: ret.append(i)
|
||||
return ret
|
||||
|
||||
def get_lines_from_file(infile,what="",trim_comments=False):
|
||||
if what != "":
|
||||
qmsg("Getting %s from file '%s'" % (what,infile))
|
||||
f = open_file_or_exit(infile,'r')
|
||||
lines = f.read().splitlines(); f.close()
|
||||
if remove_comments:
|
||||
import re
|
||||
# re.sub(pattern, repl, string, count=0, flags=0)
|
||||
ret = []
|
||||
for i in lines:
|
||||
i = re.sub('#.*','',i,1)
|
||||
i = re.sub('\s+$','',i)
|
||||
if i: ret.append(i)
|
||||
return ret
|
||||
else:
|
||||
return lines
|
||||
lines = f.read().splitlines()
|
||||
f.close()
|
||||
return remove_comments(lines) if trim_comments else lines
|
||||
|
||||
|
||||
def get_data_from_file(infile,what="data",dash=False):
|
||||
|
|
@ -729,7 +582,7 @@ def get_data_from_file(infile,what="data",dash=False):
|
|||
return data
|
||||
|
||||
|
||||
def _get_seed_from_seed_data(words):
|
||||
def get_seed_from_seed_data(words):
|
||||
|
||||
if not _check_mmseed_format(words):
|
||||
msg("Invalid %s data" % g.seed_ext)
|
||||
|
|
@ -741,7 +594,7 @@ def _get_seed_from_seed_data(words):
|
|||
chk = make_chksum_6(seed_b58)
|
||||
vmsg_r("Validating %s checksum..." % g.seed_ext)
|
||||
|
||||
if _compare_checksums(chk, "from seed", stored_chk, "from input"):
|
||||
if compare_checksums(chk, "from seed", stored_chk, "from input"):
|
||||
seed = b58decode_pad(seed_b58)
|
||||
if seed == False:
|
||||
msg("Invalid b58 number: %s" % val)
|
||||
|
|
@ -782,25 +635,6 @@ def get_bitcoind_passphrase(prompt,opts):
|
|||
echo=True if 'echo_passphrase' in opts else False)
|
||||
|
||||
|
||||
def get_seed_from_wallet(
|
||||
infile,
|
||||
opts,
|
||||
prompt="Enter {} wallet passphrase: ".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)
|
||||
|
||||
passwd = get_mmgen_passphrase(prompt,opts)
|
||||
|
||||
key = make_key(passwd, salt, hash_preset)
|
||||
|
||||
return decrypt_seed(enc_seed, key, metadata[0], metadata[1])
|
||||
|
||||
|
||||
def check_data_fits_file_at_offset(fname,offset,dlen,action):
|
||||
# TODO: Check for Windows
|
||||
import os, stat
|
||||
|
|
@ -835,236 +669,6 @@ def get_hidden_incog_data(opts):
|
|||
"Data read from file")
|
||||
return data
|
||||
|
||||
def get_seed_from_incog_wallet(
|
||||
infile,
|
||||
opts,
|
||||
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
|
||||
silent=False,
|
||||
hex_input=False
|
||||
):
|
||||
|
||||
what = "incognito wallet data"
|
||||
|
||||
if "from_incog_hidden" in opts:
|
||||
d = get_hidden_incog_data(opts)
|
||||
else:
|
||||
d = get_data_from_file(infile,what)
|
||||
if hex_input:
|
||||
try:
|
||||
d = unhexlify("".join(d.split()).strip())
|
||||
except:
|
||||
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]
|
||||
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]))
|
||||
)
|
||||
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)))
|
||||
qmsg("Check the applicable value against your records.")
|
||||
vmsg(cmessages['incog_iv_id_hidden' if "from_incog_hidden" in opts
|
||||
else 'incog_iv_id'])
|
||||
|
||||
passwd = get_mmgen_passphrase(prompt,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)
|
||||
|
||||
# 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)
|
||||
|
||||
salt,enc_seed = d[0:g.salt_len], d[g.salt_len:]
|
||||
|
||||
key = make_key(passwd, salt, hp, "main key")
|
||||
vmsg("Key ID: %s" % make_chksum_8(key))
|
||||
|
||||
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'])
|
||||
|
||||
return seed
|
||||
|
||||
|
||||
def make_key(passwd, salt, hash_preset, what="key"):
|
||||
|
||||
vmsg_r("Generating %s. Please wait..." % what)
|
||||
key = _scrypt_hash_passphrase(passwd, salt, hash_preset)
|
||||
vmsg("done")
|
||||
if g.debug: print "Key: %s" % hexlify(key)
|
||||
return key
|
||||
|
||||
|
||||
def decrypt_seed(enc_seed, key, seed_id, key_id):
|
||||
|
||||
vmsg("Checking key...")
|
||||
chk1 = make_chksum_8(key)
|
||||
if key_id:
|
||||
if not _compare_checksums(chk1, "of key", key_id, "in header"):
|
||||
msg("Incorrect passphrase")
|
||||
return False
|
||||
|
||||
dec_seed = decrypt_data(enc_seed, key, iv=1, what="seed")
|
||||
|
||||
chk2 = make_chksum_8(dec_seed)
|
||||
|
||||
if seed_id:
|
||||
if _compare_checksums(chk2,"of decrypted seed",seed_id,"in header"):
|
||||
qmsg("Passphrase is OK")
|
||||
else:
|
||||
if not g.debug:
|
||||
msg_r("Checking key ID...")
|
||||
if _compare_checksums(chk1, "of key", key_id, "in header"):
|
||||
msg("Key ID is correct but decryption of seed failed")
|
||||
else:
|
||||
msg("Incorrect passphrase")
|
||||
|
||||
return False
|
||||
# else:
|
||||
# qmsg("Generated IDs (Seed/Key): %s/%s" % (chk2,chk1))
|
||||
|
||||
if g.debug: print "Decrypted seed: %s" % hexlify(dec_seed)
|
||||
|
||||
return dec_seed
|
||||
|
||||
|
||||
def decrypt_data(enc_data, key, iv=1, what="data"):
|
||||
|
||||
vmsg("Decrypting %s with key..." % what)
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import Counter
|
||||
|
||||
c = AES.new(key, AES.MODE_CTR,
|
||||
counter=Counter.new(g.aesctr_iv_len*8,initial_value=iv))
|
||||
|
||||
return c.decrypt(enc_data)
|
||||
|
||||
|
||||
|
||||
def _get_words(infile,what,prompt,opts):
|
||||
if infile:
|
||||
return _get_words_from_file(infile,what)
|
||||
else:
|
||||
return _get_words_from_user(prompt,opts)
|
||||
|
||||
|
||||
def get_seed(infile,opts,silent=False):
|
||||
|
||||
ext = get_extension(infile)
|
||||
|
||||
if ext == g.mn_ext: source = "mnemonic"
|
||||
elif ext == g.brain_ext: source = "brainwallet"
|
||||
elif ext == g.seed_ext: source = "seed"
|
||||
elif ext == g.wallet_ext: source = "wallet"
|
||||
elif ext == g.incog_ext: source = "incognito wallet"
|
||||
elif ext == g.incog_hex_ext: source = "incognito wallet"
|
||||
elif 'from_mnemonic' in opts: source = "mnemonic"
|
||||
elif 'from_brain' in opts: source = "brainwallet"
|
||||
elif 'from_seed' in opts: source = "seed"
|
||||
elif 'from_incog' in opts: source = "incognito wallet"
|
||||
else:
|
||||
if infile: msg(
|
||||
"Invalid file extension for file: %s\nValid extensions: '.%s'" %
|
||||
(infile, "', '.".join(g.seedfile_exts)))
|
||||
else: msg("No seed source type specified and no file supplied")
|
||||
sys.exit(2)
|
||||
|
||||
if source == "mnemonic":
|
||||
prompt = "Enter mnemonic: "
|
||||
words = _get_words(infile,"mnemonic data",prompt,opts)
|
||||
wl = get_default_wordlist()
|
||||
from mmgen.mnemonic import get_seed_from_mnemonic
|
||||
seed = get_seed_from_mnemonic(words,wl)
|
||||
elif source == "brainwallet":
|
||||
if 'from_brain' not in opts:
|
||||
msg("'--from-brain' parameters must be specified for brainwallet file")
|
||||
sys.exit(2)
|
||||
prompt = "Enter brainwallet passphrase: "
|
||||
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
|
||||
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
|
||||
seed = get_seed_from_incog_wallet(infile, opts, silent=silent, hex_input=h)
|
||||
|
||||
|
||||
if infile and not seed and (
|
||||
source == "seed" or source == "mnemonic" or source == "incognito wallet"):
|
||||
msg("Invalid %s file '%s'" % (source,infile))
|
||||
sys.exit(2)
|
||||
|
||||
if g.debug: print "Seed: %s" % hexlify(seed)
|
||||
|
||||
return seed
|
||||
|
||||
# Repeat if entered data is invalid
|
||||
def get_seed_retry(infile,opts):
|
||||
silent = False
|
||||
while True:
|
||||
seed = get_seed(infile,opts,silent=silent)
|
||||
silent = True
|
||||
if seed: return seed
|
||||
|
||||
|
||||
def do_pager(text):
|
||||
|
||||
pagers = ["less","more"]
|
||||
shell = False
|
||||
|
||||
from os import environ
|
||||
|
||||
# Hack for MS Windows command line (i.e. non CygWin) environment
|
||||
# When 'shell' is true, Windows aborts the calling program if executable
|
||||
# not found.
|
||||
# When 'shell' is false, an exception is raised, invoking the fallback
|
||||
# 'print' instead of the pager.
|
||||
# We risk assuming that "more" will always be available on a stock
|
||||
# Windows installation.
|
||||
if sys.platform.startswith("win") and 'HOME' not in environ:
|
||||
shell = True
|
||||
pagers = ["more"]
|
||||
|
||||
if 'PAGER' in environ and environ['PAGER'] != pagers[0]:
|
||||
pagers = [environ['PAGER']] + pagers
|
||||
|
||||
for pager in pagers:
|
||||
end = "" if pager == "less" else "\n(end of text)\n"
|
||||
try:
|
||||
from subprocess import Popen, PIPE, STDOUT
|
||||
p = Popen([pager], stdin=PIPE, shell=shell)
|
||||
except: pass
|
||||
else:
|
||||
try:
|
||||
p.communicate(text+end+"\n")
|
||||
except KeyboardInterrupt:
|
||||
# Has no effect. Why?
|
||||
if pager != "less":
|
||||
msg("\n(User interrupt)\n")
|
||||
finally:
|
||||
msg_r("\r")
|
||||
break
|
||||
else: print text+end
|
||||
|
||||
|
||||
def export_to_hidden_incog(incog_enc,opts):
|
||||
outfile,offset = opts['export_incog_hidden'].split(",") #Already sanity-checked
|
||||
|
|
@ -1081,46 +685,50 @@ def export_to_hidden_incog(incog_enc,opts):
|
|||
(os.path.relpath(outfile),offset))
|
||||
|
||||
|
||||
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)) +
|
||||
hexlify(data[i*gw:i*gw+gw]) +
|
||||
(" " if (i+1) % cols else "\n")
|
||||
for i in range(len(data)/gw + r)
|
||||
]
|
||||
).rstrip()
|
||||
from mmgen.term import kb_hold_protect,get_char
|
||||
|
||||
def decode_pretty_hexdump(data):
|
||||
import re
|
||||
lines = [re.sub('^\d+:\s+','',l) for l in data.split("\n")]
|
||||
return unhexlify("".join(("".join(lines).split())))
|
||||
def my_raw_input(prompt,echo=True):
|
||||
try:
|
||||
if echo:
|
||||
reply = raw_input(prompt)
|
||||
else:
|
||||
from getpass import getpass
|
||||
reply = getpass(prompt)
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
kb_hold_protect()
|
||||
return reply
|
||||
|
||||
|
||||
def wallet_to_incog_data(infile,opts):
|
||||
def user_confirm(prompt,default_yes=False,verbose=False):
|
||||
|
||||
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]
|
||||
q = "(Y/n)" if default_yes else "(y/N)"
|
||||
|
||||
passwd = get_mmgen_passphrase("Enter mmgen passphrase: ",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)
|
||||
while True:
|
||||
reply = get_char("%s %s: " % (prompt, q)).strip("\n\r")
|
||||
|
||||
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
|
||||
if not reply:
|
||||
if default_yes: msg(""); return True
|
||||
else: msg(""); return False
|
||||
elif reply in 'yY': msg(""); return True
|
||||
elif reply in 'nN': msg(""); return False
|
||||
else:
|
||||
if verbose: msg("\nInvalid reply")
|
||||
else: msg_r("\r")
|
||||
|
||||
|
||||
def prompt_and_get_char(prompt,chars,enter_ok=False,verbose=False):
|
||||
|
||||
while True:
|
||||
reply = get_char("%s: " % prompt).strip("\n\r")
|
||||
|
||||
if reply in chars or (enter_ok and not reply):
|
||||
msg("")
|
||||
return reply
|
||||
|
||||
if verbose: msg("\nInvalid reply")
|
||||
else: msg_r("\r")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "util.py"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue