Incognito wallet is 48, 56 or 64 bytes of apparently random data. Allows user to hide wallet data in a pre-existing file or on a disk partition (preferably filled in advance with random data). Can be used to hide wallet securely in unencrypted cloud storage or on paper, without revealing the nature of the data. Data may be written at a user-specified offset into the file or partition, in which case user must remember the offset. Each export operation uses a new random init vector to create different data each time. This allows the user to hide wallets at different locations on the Net without detection. User must remember hash preset in addition to passphrase (though trial and error can be used if it's forgotten). Fully integrated with address generation and tx signing operations.
182 lines
6.4 KiB
Python
Executable file
182 lines
6.4 KiB
Python
Executable file
#!/usr/bin/env python
|
|
#
|
|
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
|
# Copyright (C) 2013 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/>.
|
|
"""
|
|
mmgen-walletgen: Generate a mmgen deterministic wallet
|
|
"""
|
|
|
|
import sys, os
|
|
from hashlib import sha256
|
|
|
|
from mmgen.Opts import *
|
|
from mmgen.license import *
|
|
import mmgen.config as g
|
|
from mmgen.walletgen import *
|
|
from mmgen.util import *
|
|
prog_name = sys.argv[0].split("/")[-1]
|
|
|
|
help_data = {
|
|
'prog_name': prog_name,
|
|
'desc': "Generate a {} deterministic wallet".format(g.proj_name),
|
|
'usage': "[opts] [infile]",
|
|
'options': """
|
|
-h, --help Print this help message
|
|
-d, --outdir d Specify an alternate directory 'd' for output
|
|
-e, --echo-passphrase Print passphrase to screen when typing it
|
|
-H, --show-hash-presets Show information on available hash presets
|
|
-l, --seed-len n Create seed of length 'n'. Options: {}
|
|
(default: {})
|
|
-L, --label l Label to identify this wallet (32 chars max.
|
|
Allowed symbols: A-Z, a-z, 0-9, " ", "_", ".")
|
|
-p, --hash-preset p Use scrypt.hash() parameters from preset 'p'
|
|
(default: '{}')
|
|
-P, --passwd-file f Get passphrase from file 'f'
|
|
-q, --quiet Produce quieter output; overwrite files without
|
|
prompting
|
|
-u, --usr-randlen n Get 'n' characters of randomness from the user
|
|
(default: {})
|
|
-v, --verbose Produce more verbose output
|
|
|
|
-b, --from-brain l,p Generate wallet from a user-created passphrase,
|
|
i.e. a "brainwallet", using seed length 'l' and
|
|
hash preset 'p' (comma-separated)
|
|
-g, --from-incognito Generate wallet from an incognito-format wallet
|
|
-m, --from-mnemonic Generate wallet from an Electrum-like mnemonic
|
|
-s, --from-seed Generate wallet from a seed in .{S} format
|
|
|
|
By default (i.e. when invoked without any of the '--from-<what>' options),
|
|
{} generates a wallet based on a random seed.
|
|
|
|
Data for the --from-<what> options will be taken from <infile> if <infile>
|
|
is specified. Otherwise, the user will be prompted to enter the data.
|
|
|
|
For passphrases all combinations of whitespace are equal, and leading and
|
|
trailing space are ignored. This permits reading passphrase data from a
|
|
multi-line file with free spacing and indentation. This is particularly
|
|
convenient for long brainwallet passphrases, for example.
|
|
|
|
BRAINWALLET NOTE:
|
|
|
|
As brainwallets require especially strong hashing to thwart dictionary
|
|
attacks, the brainwallet hash preset must be specified by the user, using
|
|
the 'p' parameter of the '--from-brain' option. This preset should be
|
|
stronger than the one used for hashing the seed (i.e. the default value or
|
|
the one specified in the '--hash-preset' option).
|
|
|
|
The '--from-brain' option also requires the user to specify a seed length
|
|
(the 'l' parameter), which overrides both the default and any one given in
|
|
the '--seed-len' option.
|
|
|
|
For a brainwallet passphrase to always generate the same keys and
|
|
addresses, the same 'l' and 'p' parameters to '--from-brain' must be used
|
|
in all future invocations with that passphrase.
|
|
""".format(
|
|
",".join([str(i) for i in g.seed_lens]),
|
|
g.seed_len,
|
|
g.hash_preset,
|
|
g.usr_randlen,
|
|
prog_name,
|
|
S=g.seed_ext,
|
|
)
|
|
}
|
|
|
|
short_opts = "hd:eHl:L:p:P:qu:vb:gms"
|
|
long_opts = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
|
|
"label=","hash_preset=","passwd_file=","quiet","usr_randlen=","verbose",\
|
|
"from_brain=","from_incognito","from_mnemonic","from_seed"
|
|
|
|
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
|
if 'quiet' in opts: g.quiet = True
|
|
if 'verbose' in opts: g.verbose = True
|
|
if 'show_hash_presets' in opts: show_hash_presets()
|
|
|
|
check_opts(opts,long_opts)
|
|
|
|
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
|
|
|
|
if len(cmd_args) == 1:
|
|
infile = cmd_args[0]
|
|
check_infile(infile)
|
|
ext = infile.split(".")[-1]
|
|
ok_exts = g.seedfile_exts
|
|
for e in ok_exts:
|
|
if e == ext: break
|
|
else:
|
|
msg(
|
|
"Input file must have one of the following extensions: .%s" % ", .".join(ok_exts))
|
|
sys.exit(1)
|
|
elif len(cmd_args) == 0:
|
|
infile = ""
|
|
else: usage(help_data)
|
|
|
|
# Begin execution
|
|
|
|
do_license_msg()
|
|
|
|
qmsg_r("Acquiring random data from your computer...")
|
|
|
|
from time import sleep
|
|
sleep(0.25)
|
|
|
|
try:
|
|
from Crypto import Random
|
|
r = Random.new()
|
|
os_rand_data = (r.read(256),r.read(128))
|
|
except:
|
|
msg("FAILED\nUnable to generate random numbers. Exiting")
|
|
sys.exit(2)
|
|
|
|
qmsg("OK")
|
|
|
|
if g.debug: display_os_random_data(os_rand_data)
|
|
|
|
usr_keys,key_timings = get_random_data_from_user(opts)
|
|
qmsg("")
|
|
|
|
if g.debug: display_user_random_data(usr_keys,key_timings)
|
|
|
|
usr_rand_data = sha256(usr_keys).digest() + \
|
|
sha256("".join(key_timings)).digest()
|
|
|
|
for i in 'from_mnemonic','from_brain','from_seed','from_incognito':
|
|
if infile or (i in opts):
|
|
seed = get_seed_retry(infile,opts)
|
|
if "from_incognito" in opts or get_extension(infile) == g.incog_ext:
|
|
qmsg(cmessages['incognito'] % make_chksum_8(seed))
|
|
else: qmsg("")
|
|
break
|
|
else:
|
|
# Truncate random data for smaller seed lengths
|
|
seed = os_rand_data[0] + usr_rand_data
|
|
seed = sha256(seed).digest()[:opts['seed_len']/8]
|
|
|
|
salt = os_rand_data[1] + usr_rand_data
|
|
salt = sha256(salt).digest()[:g.salt_len]
|
|
|
|
qmsg("""Now you must choose a passphrase to encrypt the seed 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.
|
|
""" % opts['hash_preset'])
|
|
|
|
passwd = get_new_passphrase("{} wallet passphrase".format(g.proj_name), opts)
|
|
|
|
key = make_key(passwd, salt, opts['hash_preset'])
|
|
|
|
enc_seed = encrypt_seed(seed, key)
|
|
|
|
write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)
|