Version 0.7.0
This commit is contained in:
parent
19ed9d8096
commit
08c5b76805
28 changed files with 934 additions and 627 deletions
24
MANIFEST
24
MANIFEST
|
|
@ -1,7 +1,7 @@
|
|||
# file GENERATED by distutils, do NOT edit
|
||||
__init__.py
|
||||
mmgen-addrgen
|
||||
mmgen-addrimport
|
||||
mmgen-keygen
|
||||
mmgen-passchg
|
||||
mmgen-pywallet
|
||||
mmgen-txcreate
|
||||
|
|
@ -19,8 +19,9 @@ mmgen/license.py
|
|||
mmgen/mn_electrum.py
|
||||
mmgen/mn_tirosh.py
|
||||
mmgen/mnemonic.py
|
||||
mmgen/term.py
|
||||
mmgen/tx.py
|
||||
mmgen/utils.py
|
||||
mmgen/util.py
|
||||
mmgen/walletgen.py
|
||||
mmgen/rpc/__init__.py
|
||||
mmgen/rpc/config.py
|
||||
|
|
@ -29,13 +30,12 @@ mmgen/rpc/data.py
|
|||
mmgen/rpc/exceptions.py
|
||||
mmgen/rpc/proxy.py
|
||||
mmgen/rpc/util.py
|
||||
scripts/bitcoind-walletunlock.py
|
||||
scripts/deinstall.sh
|
||||
tests/addr.py
|
||||
tests/bitcoin.py
|
||||
tests/mn_electrum.py
|
||||
tests/mn_tirosh.py
|
||||
tests/mnemonic.py
|
||||
tests/test.py
|
||||
tests/utils.py
|
||||
tests/walletgen.py
|
||||
mmgen/tests/__init__.py
|
||||
mmgen/tests/addr.py
|
||||
mmgen/tests/bitcoin.py
|
||||
mmgen/tests/mn_electrum.py
|
||||
mmgen/tests/mn_tirosh.py
|
||||
mmgen/tests/mnemonic.py
|
||||
mmgen/tests/test.py
|
||||
mmgen/tests/util.py
|
||||
mmgen/tests/walletgen.py
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ addresses can be tracked and spent as well, creating an easy migration path from
|
|||
other wallets.
|
||||
|
||||
To track address balances, MMGen relies on a bitcoin daemon modified by
|
||||
Bitcoin's core developer team to support watch-only addresses. This feature
|
||||
is soon to be included in the mainline Satoshi build. In the meantime,
|
||||
Bitcoin core developers to support watch-only addresses. This feature
|
||||
will one day be included in the mainline Satoshi build. In the meantime,
|
||||
instructions are provided below for compiling the modified bitcoind. Under
|
||||
Linux, this is a trivial task for even a casual user. Unfortunately, the same
|
||||
can't be said for Windows, but the thoroughly tested, step-by-step Windows build
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ time-consuming process of compiling the whole boost package.
|
|||
|
||||
#### 5. Build Bitcoind:
|
||||
|
||||
Download Sipa's watchonly bitcoind [zip archive][05] (commit a13f1e8 [[check][]])
|
||||
Download Sipa's watchonly bitcoind [zip archive][05] (commit #a13f1e8 [[check][]])
|
||||
from GitHub and unpack it. At the MSYS prompt, run:
|
||||
|
||||
$ cd /c/bitcoin-watchonly
|
||||
|
|
|
|||
|
|
@ -154,7 +154,7 @@ sending it to the addresses labeled "Storage 1", "Storage 2" and "Storage 3"
|
|||
|
||||
To refresh your memory, here are the three addresses in question:
|
||||
|
||||
$ cat my_addrs
|
||||
$ cat my.addrs
|
||||
# My first MMGen addresses
|
||||
89ABCDEF {
|
||||
1 16bNmyYISiptuvJG3X7MPwiiS4HYvD7ksE Donations
|
||||
|
|
@ -175,11 +175,7 @@ the change amount (3.399 BTC in this case) automatically.
|
|||
Alternatively, and more conveniently, you can list your three addresses in MMGen
|
||||
format:
|
||||
|
||||
$ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4 my_addrs
|
||||
|
||||
Note that an MMGen address file containing the requested output addresses must
|
||||
be provided on the command line. Any extra addresses in the file will be
|
||||
ignored.
|
||||
$ mmgen-txcreate 89ABCDEF:2,3.3 89ABCDEF:3,3.3 89ABCDEF:4
|
||||
|
||||
Now hit ENTER, choose the transaction's input from the list (10 BTC, address
|
||||
1F9495H8EJL..., txid 04f97185...,2), and confirm. If all goes well,
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ the version should be 1.48 or greater):
|
|||
libboost-test-dev
|
||||
libboost-thread-dev
|
||||
|
||||
Download the bitcoin-watchonly [zip archive][00] (commit a13f1e8 [[check][]])
|
||||
Download the bitcoin-watchonly [zip archive][00] (commit #a13f1e8 [[check][]])
|
||||
from GitHub, configure, and build:
|
||||
|
||||
$ unzip watchonly.zip
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ MMGen = Multi-Mode GENerator
|
|||
============================
|
||||
##### a Bitcoin cold storage solution for the command line
|
||||
|
||||
#### A word on text editors:
|
||||
### A word on text editors:
|
||||
|
||||
The text file editors that come with Windows, "edit" and "notepad", are
|
||||
unsuitable for editing source code for many reasons, but above all because they
|
||||
|
|
|
|||
|
|
@ -121,6 +121,8 @@ if invoked_as == "addrgen":
|
|||
opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
|
||||
|
||||
if 'show_hash_presets' in opts: show_hash_presets()
|
||||
if 'quiet' in opts: g.quiet = True
|
||||
if 'verbose' in opts: g.verbose = True
|
||||
|
||||
opts['gen_what'] = gen_what
|
||||
|
||||
|
|
@ -143,10 +145,10 @@ addr_list = parse_address_list(addr_list_arg)
|
|||
|
||||
if not addr_list: sys.exit(2)
|
||||
|
||||
if not 'quiet' in opts: do_license_msg()
|
||||
do_license_msg()
|
||||
|
||||
# Interact with user:
|
||||
if invoked_as == "keygen" and not 'quiet' in opts:
|
||||
if invoked_as == "keygen" and not g.quiet:
|
||||
confirm_or_exit(cmessages['unencrypted_secret_keys'], 'continue')
|
||||
|
||||
# Generate data:
|
||||
|
|
@ -163,7 +165,7 @@ addr_data_str = format_addr_data(addr_data, seed_id, opts)
|
|||
|
||||
# Output data:
|
||||
if 'stdout' in opts:
|
||||
if invoked_as == "keygen" and not 'quiet' in opts:
|
||||
if invoked_as == "keygen" and not g.quiet:
|
||||
confirm = True
|
||||
else: confirm = False
|
||||
write_to_stdout(addr_data_str,"secret keys",confirm)
|
||||
|
|
|
|||
|
|
@ -23,9 +23,8 @@ mmgen-addrimport: Import addresses into a bitcoind watching wallet.
|
|||
import sys
|
||||
from mmgen.Opts import *
|
||||
from mmgen.license import *
|
||||
from mmgen.util import check_infile,confirm_or_exit,msg,msg_r,secs_to_hms,get_lines_from_file
|
||||
from mmgen.util import *
|
||||
from mmgen.tx import connect_to_bitcoind,parse_addrs_file
|
||||
from mmgen.bitcoin import verify_addr
|
||||
|
||||
help_data = {
|
||||
'prog_name': sys.argv[0].split("/")[-1],
|
||||
|
|
@ -43,6 +42,7 @@ short_opts = "hl:q"
|
|||
long_opts = "help", "addrlist=", "quiet"
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,"".join(short_opts),long_opts)
|
||||
if 'quiet' in opts: g.quiet = True
|
||||
|
||||
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)")
|
||||
|
|
@ -57,28 +57,27 @@ else:
|
|||
seed_id,addr_data = "",[]
|
||||
|
||||
if 'addrlist' in opts:
|
||||
l = get_lines_from_file(
|
||||
opts['addrlist'],"non-mmgen addresses",remove_comments=True)
|
||||
l = get_lines_from_file(opts['addrlist'],"non-mmgen addresses",
|
||||
remove_comments=True)
|
||||
addr_data += [(None,i) for i in l]
|
||||
|
||||
msg_r("Validating addresses...")
|
||||
from mmgen.bitcoin import verify_addr
|
||||
qmsg_r("Validating addresses...")
|
||||
for i in addr_data:
|
||||
if not verify_addr(i[1]):
|
||||
if not verify_addr(i[1],verbose=True):
|
||||
msg("%s: invalid address" % i)
|
||||
sys.exit(2)
|
||||
|
||||
msg("OK")
|
||||
qmsg("OK")
|
||||
|
||||
import mmgen.config as g
|
||||
g.http_timeout = 3600
|
||||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
if not 'quiet' in opts:
|
||||
message = """
|
||||
m = """
|
||||
Importing addresses can take a long time (>30 min.) on a low-powered computer.
|
||||
"""
|
||||
confirm_or_exit(message, "continue", expect="YES")
|
||||
""".strip()
|
||||
confirm_or_exit(m, "continue", expect="YES")
|
||||
|
||||
import threading
|
||||
import time
|
||||
|
|
|
|||
|
|
@ -39,6 +39,8 @@ help_data = {
|
|||
-p, --hash-preset p Change scrypt.hash() parameters to preset 'p'
|
||||
(default: '{}')
|
||||
-P, --passwd-file f Get new passphrase from file 'f'
|
||||
-q, --quiet Suppress warnings; overwrite files without
|
||||
prompting
|
||||
-v, --verbose Produce more verbose output
|
||||
|
||||
NOTE: The key ID will change if either the passphrase or hash preset
|
||||
|
|
@ -46,23 +48,24 @@ NOTE: The key ID will change if either the passphrase or hash preset
|
|||
""".format(g.hash_preset)
|
||||
}
|
||||
|
||||
short_opts = "hd:HkL:p:P:v"
|
||||
short_opts = "hd:HkL:p:P:qv"
|
||||
long_opts = "help","outdir=","show_hash_presets","keep_old_passphrase",\
|
||||
"label=","hash_preset=","passwd_file=","verbose"
|
||||
"label=","hash_preset=","passwd_file=","quiet","verbose"
|
||||
|
||||
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
|
||||
check_opts(opts,long_opts)
|
||||
|
||||
if 'show_hash_presets' in opts: show_hash_presets()
|
||||
|
||||
check_opts(opts,long_opts)
|
||||
|
||||
if len(cmd_args) != 1:
|
||||
msg("One input file must be specified")
|
||||
sys.exit(2)
|
||||
infile = cmd_args[0]
|
||||
|
||||
# Old key:
|
||||
label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile,opts)
|
||||
label,metadata,hash_preset,salt,enc_seed = get_data_from_wallet(infile)
|
||||
seed_id,key_id = metadata[:2]
|
||||
|
||||
# Repeat on incorrect pw entry
|
||||
|
|
@ -86,7 +89,7 @@ else: opts['label'] = label # Copy the old label
|
|||
|
||||
if 'hash_preset' in opts:
|
||||
if hash_preset != opts['hash_preset']:
|
||||
msg("Hash preset has changed (%s -> %s)" %
|
||||
qmsg("Hash preset has changed (%s -> %s)" %
|
||||
(hash_preset, opts['hash_preset']))
|
||||
changed['preset'] = True
|
||||
else:
|
||||
|
|
@ -100,14 +103,14 @@ else:
|
|||
new_passwd = get_new_passphrase("new passphrase", opts)
|
||||
|
||||
if new_passwd == passwd:
|
||||
msg("Passphrase is unchanged")
|
||||
qmsg("Passphrase is unchanged")
|
||||
else:
|
||||
msg("Passphrase has changed")
|
||||
qmsg("Passphrase has changed")
|
||||
passwd = new_passwd
|
||||
changed['passwd'] = True
|
||||
|
||||
if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
|
||||
msg("Will update salt and key ID")
|
||||
qmsg("Will update salt and key ID")
|
||||
|
||||
from hashlib import sha256
|
||||
from Crypto import Random
|
||||
|
|
@ -115,9 +118,9 @@ if 'preset' in changed or 'passwd' in changed: # Update key ID, salt
|
|||
salt = sha256(salt + Random.new().read(128)).digest()[:g.salt_len]
|
||||
key = make_key(passwd, salt, opts['hash_preset'])
|
||||
new_key_id = make_chksum_8(key)
|
||||
msg("Key ID changed: %s -> %s" % (key_id,new_key_id))
|
||||
qmsg("Key ID changed: %s -> %s" % (key_id,new_key_id))
|
||||
key_id = new_key_id
|
||||
enc_seed = encrypt_seed(seed, key, opts)
|
||||
enc_seed = encrypt_seed(seed, key)
|
||||
elif not 'label' in changed:
|
||||
msg("Data unchanged. No file will be written")
|
||||
sys.exit(2)
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ mmgen-pywallet: Dump contents of a bitcoind wallet to file
|
|||
|
||||
from mmgen.Opts import *
|
||||
from mmgen.util import msg
|
||||
import mmgen.config as g
|
||||
from bsddb.db import *
|
||||
import sys, time
|
||||
import json
|
||||
|
|
@ -81,21 +82,18 @@ help_data = {
|
|||
-a, --addrs Dump all addresses (flat list)
|
||||
-K, --keysforaddrs f Dump private keys for addresses listed in file 'f'
|
||||
-P, --passwd-file f Get passphrase from file 'f'
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-S, --stdout Dump to stdout rather than file
|
||||
"""
|
||||
}
|
||||
|
||||
short_opts = "hd:ejkaK:P:qS"
|
||||
short_opts = "hd:ejkaK:P:S"
|
||||
long_opts = "help","outdir=","echo_passphrase","json","keys","addrs",\
|
||||
"keysforaddrs=","passwd_file=","quiet","stdout"
|
||||
"keysforaddrs=","passwd_file=","stdout"
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
from mmgen.util import check_infile
|
||||
|
||||
check_opts(opts,long_opts)
|
||||
|
||||
from mmgen.util import check_infile
|
||||
if len(cmd_args) == 1:
|
||||
check_infile(cmd_args[0])
|
||||
else:
|
||||
|
|
@ -1683,14 +1681,11 @@ elif 'addrs' in opts:
|
|||
|
||||
elif 'keysforaddrs' in opts:
|
||||
from mmgen.util import get_lines_from_file
|
||||
usr_addrs = get_lines_from_file(opts['keysforaddrs'],"addresses")
|
||||
for addr in usr_addrs:
|
||||
try:
|
||||
idx = wallet_addrs.index(addr)
|
||||
data.append(json_db['keys'][idx]['sec'])
|
||||
except:
|
||||
msg("WARNING: Address '%s' not found" % addr)
|
||||
data,ext,what = sorted(data),"keys","private keys"
|
||||
usr_addrs = set(get_lines_from_file(opts['keysforaddrs'],"addresses",remove_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):
|
||||
msg("Warning: not all requested keys found")
|
||||
|
||||
len_arg = "%s" % len(wallet_addrs) \
|
||||
if len(data) == len(wallet_addrs) or ext == "json" \
|
||||
|
|
@ -1703,8 +1698,7 @@ data = "\n".join(data) + "\n"
|
|||
|
||||
# Output data
|
||||
if 'stdout' in opts:
|
||||
if 'addrs' in opts or 'quiet' in opts: confirm = False
|
||||
else: confirm = True
|
||||
confirm = False if 'addrs' in opts else True
|
||||
write_to_stdout(data,"secret keys",confirm)
|
||||
elif not sys.stdout.isatty():
|
||||
write_to_stdout(data,"secret keys",confirm=False)
|
||||
|
|
|
|||
136
mmgen-txcreate
136
mmgen-txcreate
|
|
@ -51,77 +51,82 @@ via an interactive menu.
|
|||
Ages of transactions are approximate based on an average block creation
|
||||
interval of %s minutes.
|
||||
|
||||
Addresses on the command line can be Bitcoin addresses or MMGen
|
||||
addresses in the form <seed ID>:<number>
|
||||
Addresses on the command line can be Bitcoin addresses or MMGen addresses
|
||||
of the form <seed ID>:<number>.
|
||||
|
||||
To send all inputs (minus TX fee) to a single output, specify one address
|
||||
with no amount on the command line.
|
||||
""" % (Decimal(g.tx_fee),g.mins_per_block)
|
||||
}
|
||||
|
||||
short_opts = "ha:d:eiqf:"
|
||||
long_opts = "help","addr_file","outdir=","echo_passphrase","info","quiet",\
|
||||
"tx_fee="
|
||||
short_opts = "hd:eiqf:"
|
||||
long_opts = "help","outdir=","echo_passphrase","info","quiet","tx_fee="
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
if 'quiet' in opts: g.quiet = True
|
||||
check_opts(opts,long_opts)
|
||||
|
||||
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
|
||||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
if not 'info' in opts:
|
||||
|
||||
outputs,addr_files,change_addr = [],[],""
|
||||
tx_out,addr_data,b2m_map,acct_data,change_addr = {},[],{},[],""
|
||||
|
||||
for a in cmd_args:
|
||||
if a.split(".")[-1] == g.addrfile_ext:
|
||||
for a in [i for i in cmd_args if match_ext(i,g.addrfile_ext)]:
|
||||
if match_ext(a,g.addrfile_ext):
|
||||
check_infile(a)
|
||||
addr_files.append(a)
|
||||
elif "," in a:
|
||||
outputs.append(a)
|
||||
else:
|
||||
addr_data.append(parse_addrs_file(a))
|
||||
|
||||
def mm2btc_addr_proc(c,mmadr,acct_data,addr_data,b2m_map):
|
||||
btaddr,label = mmgen_addr_to_walletd(c,mmadr,acct_data)
|
||||
if not btaddr:
|
||||
btaddr,label = mmgen_addr_to_addr_data(mmadr,addr_data)
|
||||
b2m_map[btaddr] = mmadr,label
|
||||
return btaddr
|
||||
|
||||
for a in [i for i in cmd_args if not match_ext(i,g.addrfile_ext)]:
|
||||
if "," in a:
|
||||
a1,a2 = a.split(",")
|
||||
if is_mmgen_addr(a1) or is_btc_addr(a1):
|
||||
if is_mmgen_addr(a1):
|
||||
btaddr = mm2btc_addr_proc(c,a1,acct_data,addr_data,b2m_map)
|
||||
if is_btc_amt(a2):
|
||||
tx_out[btaddr] = check_btc_amt(a2)
|
||||
else:
|
||||
msg("%s: invalid amount in argument '%s'" % (a2,a))
|
||||
sys.exit(2)
|
||||
else:
|
||||
msg("%s: unrecognized subargument in argument '%s'" % (a1,a))
|
||||
sys.exit(2)
|
||||
elif is_mmgen_addr(a) or is_btc_addr(a):
|
||||
if change_addr:
|
||||
msg("More than one change address specified: %s, %s" %
|
||||
(change_addr, a))
|
||||
sys.exit(2)
|
||||
change_addr = a
|
||||
if is_mmgen_addr(a):
|
||||
change_addr = mm2btc_addr_proc(c,a,acct_data,addr_data,b2m_map)
|
||||
else:
|
||||
change_addr = a
|
||||
tx_out[change_addr] = 0
|
||||
else:
|
||||
msg("%s: unrecognized argument" % a)
|
||||
sys.exit(2)
|
||||
|
||||
if not outputs:
|
||||
if not tx_out:
|
||||
msg("At least one output must be specified on the command line")
|
||||
sys.exit(2)
|
||||
|
||||
addr_data = [parse_addrs_file(f) for f in addr_files]
|
||||
|
||||
tx_fee = opts['tx_fee'] if 'tx_fee' in opts else g.tx_fee
|
||||
|
||||
try:
|
||||
tx_fee = Decimal(tx_fee)
|
||||
except:
|
||||
msg("Invalid transaction fee format: %s" % tx_fee)
|
||||
sys.exit(2)
|
||||
|
||||
tx_fee = check_btc_amt(tx_fee)
|
||||
if tx_fee > g.max_tx_fee:
|
||||
msg("Transaction fee too large: %s > %s" % (tx_fee,g.max_tx_fee))
|
||||
sys.exit(2)
|
||||
|
||||
if change_addr:
|
||||
if ":" in change_addr:
|
||||
change_addr = mmgen_addr_to_btc_addr(change_addr,addr_data)
|
||||
else:
|
||||
check_address(change_addr)
|
||||
|
||||
|
||||
tx_out = make_tx_out(outputs,addr_data)
|
||||
|
||||
for i in tx_out.keys(): check_address(i)
|
||||
for i in tx_out.values(): check_btc_amt(i)
|
||||
|
||||
tx_fee = check_btc_amt(tx_fee)
|
||||
|
||||
if g.debug: show_opts_and_cmd_args(opts,cmd_args)
|
||||
|
||||
# Begin execution
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
if not 'quiet' in opts and not 'info' in opts:
|
||||
do_license_msg(immed=True)
|
||||
if not 'info' in opts: do_license_msg(immed=True)
|
||||
|
||||
# Begin test
|
||||
# import mmgen.rpc
|
||||
|
|
@ -131,10 +136,9 @@ if not 'quiet' in opts and not 'info' in opts:
|
|||
us = c.listunspent()
|
||||
|
||||
if not us:
|
||||
msg_r("""
|
||||
msg("""
|
||||
No spendable outputs found! Import addresses with balances into your
|
||||
watch-only wallet using 'mmgen-addrimport' and then re-run this program.
|
||||
""")
|
||||
watch-only wallet using 'mmgen-addrimport' and then re-run this program.""")
|
||||
sys.exit(2)
|
||||
|
||||
# write_to_file("listunspent.json",repr(us))
|
||||
|
|
@ -146,8 +150,9 @@ total = trim_exponent(sum([i.amount for i in unspent]))
|
|||
msg("Total unspent: %s BTC (%s outputs)" % (total, len(unspent)))
|
||||
if 'info' in opts: sys.exit(0)
|
||||
|
||||
send_amt = sum(tx_out.values())
|
||||
msg("Total amount to spend: %s BTC" % send_amt)
|
||||
send_amt = sum([tx_out[i] for i in tx_out.keys()])
|
||||
msg("Total amount to spend: %s%s" % (
|
||||
(send_amt or "Unknown")," BTC" if send_amt else ""))
|
||||
|
||||
while True:
|
||||
sel_nums = select_outputs(unspent,
|
||||
|
|
@ -155,17 +160,15 @@ while True:
|
|||
msg("Selected outputs: %s" % " ".join(str(i) for i in sel_nums))
|
||||
sel_unspent = [unspent[i-1] for i in sel_nums]
|
||||
|
||||
lbls = set([verify_mmgen_label(
|
||||
i.account,return_str=True,check_label_len=True)
|
||||
for i in sel_unspent])
|
||||
lbls.discard("")
|
||||
mmaddrs = set([parse_mmgen_label(i.account)[0] for i in sel_unspent])
|
||||
mmaddrs.discard("")
|
||||
|
||||
if lbls and len(lbls) < len(sel_unspent):
|
||||
msg(txmsg['mixed_inputs'] % ", ".join(sorted(lbls)))
|
||||
if mmaddrs and len(mmaddrs) < len(sel_unspent):
|
||||
msg(txmsg['mixed_inputs'] % ", ".join(sorted(mmaddrs)))
|
||||
if not user_confirm("Accept?"):
|
||||
continue
|
||||
|
||||
total_in = trim_exponent(sum([o.amount for o in sel_unspent]))
|
||||
total_in = trim_exponent(sum([i.amount for i in sel_unspent]))
|
||||
change = trim_exponent(total_in - (send_amt + tx_fee))
|
||||
|
||||
if change >= 0:
|
||||
|
|
@ -175,28 +178,35 @@ while True:
|
|||
else:
|
||||
msg(txmsg['not_enough_btc'] % change)
|
||||
|
||||
|
||||
if change > 0 and not change_addr:
|
||||
msg(txmsg['throwaway_change'] % (change, total_in-tx_fee))
|
||||
msg(txmsg['throwaway_change'] % change)
|
||||
sys.exit(2)
|
||||
|
||||
if change_addr in tx_out and not change:
|
||||
msg("Warning: Change address will be unused as transaction produces no change")
|
||||
del tx_out[change_addr]
|
||||
|
||||
for k,v in tx_out.items(): tx_out[k] = float(v)
|
||||
|
||||
if change > 0: tx_out[change_addr] = float(change)
|
||||
|
||||
tx_in = [{"txid":i.txid, "vout":i.vout} for i in sel_unspent]
|
||||
for i in tx_out.keys(): tx_out[i] = float(tx_out[i])
|
||||
if change: tx_out[change_addr] = float(change)
|
||||
|
||||
if g.debug:
|
||||
print "tx_in:", repr(tx_in)
|
||||
print "tx_out:", repr(tx_out)
|
||||
tx_hex = c.createrawtransaction(tx_in,tx_out)
|
||||
|
||||
msg("Transaction successfully created")
|
||||
tx_hex = c.createrawtransaction(tx_in,tx_out)
|
||||
qmsg("Transaction successfully created")
|
||||
prompt = "View decoded transaction? (y)es, (N)o, (v)iew in pager"
|
||||
reply = prompt_and_get_char(prompt,"YyNnVv",enter_ok=True)
|
||||
|
||||
if reply and reply in "YyVv":
|
||||
pager = True if reply in "Vv" else False
|
||||
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,pager=pager)
|
||||
view_tx_data(c,[i.__dict__ for i in sel_unspent],tx_hex,b2m_map,pager=pager)
|
||||
|
||||
prompt = "Save transaction?"
|
||||
if user_confirm(prompt,default_yes=True):
|
||||
print_tx_to_file(tx_hex,sel_unspent,send_amt,opts)
|
||||
print_tx_to_file(tx_hex,sel_unspent,send_amt or change,b2m_map,opts)
|
||||
else:
|
||||
msg("Transaction not saved")
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ short_opts = "hd:q"
|
|||
long_opts = "help","outdir=","quiet"
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
if 'quiet' in opts: g.quiet = True
|
||||
check_opts(opts,long_opts)
|
||||
|
||||
if len(cmd_args) == 1:
|
||||
|
|
@ -71,9 +71,9 @@ except:
|
|||
msg("Invalid signed transaction data")
|
||||
sys.exit(3)
|
||||
|
||||
if not 'quiet' in opts: do_license_msg()
|
||||
do_license_msg()
|
||||
|
||||
msg("\nSigned transaction file '%s' appears valid" % infile)
|
||||
qmsg("Signed transaction file '%s' is valid" % infile)
|
||||
|
||||
warn = "Once this transaction is sent, there's no taking it back!"
|
||||
what = "broadcast this transaction to the network"
|
||||
|
|
@ -86,7 +86,6 @@ c = connect_to_bitcoind()
|
|||
|
||||
try:
|
||||
tx = c.sendrawtransaction(tx_sig)
|
||||
# tx = "deadbeef"
|
||||
except:
|
||||
msg("Unable to send transaction")
|
||||
sys.exit(3)
|
||||
|
|
|
|||
87
mmgen-txsign
87
mmgen-txsign
|
|
@ -25,7 +25,7 @@ from mmgen.Opts import *
|
|||
from mmgen.license import *
|
||||
import mmgen.config as g
|
||||
from mmgen.tx import *
|
||||
from mmgen.util import msg
|
||||
from mmgen.util import msg,qmsg
|
||||
|
||||
help_data = {
|
||||
'prog_name': sys.argv[0].split("/")[-1],
|
||||
|
|
@ -36,18 +36,19 @@ help_data = {
|
|||
-d, --outdir d Specify an alternate directory 'd' for output
|
||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
-i, --info Display information about the transaction and exit
|
||||
-I, --tx_id Display transaction ID and exit
|
||||
-I, --tx-id Display transaction ID and exit
|
||||
-k, --keys-from-file k Provide additional key data from file 'k'
|
||||
-P, --passwd-file f Get passphrase from file 'f'
|
||||
-q, --quiet Suppress warnings; overwrite files without
|
||||
prompting
|
||||
-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' (comma-separated)
|
||||
hash preset 'p'
|
||||
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
|
||||
-s, --from-seed Generate keys from a seed in .{} format
|
||||
-w, --use-wallet-dat Use the keys in the bitcoind wallet.dat file too
|
||||
-w, --use-wallet-dat Get keys from a running bitcoind
|
||||
|
||||
Transactions with either mmgen or non-mmgen input addresses may be signed.
|
||||
For non-mmgen inputs, the bitcoind wallet.dat is used as the key source.
|
||||
|
|
@ -60,9 +61,13 @@ prompted to enter the data.
|
|||
|
||||
In cases of transactions with mixed mmgen and non-mmgen inputs, non-mmgen
|
||||
keys must be supplied in a separate file (WIF format, one key per line)
|
||||
using the '--keys-from-file' option. Alternatively, one may import the
|
||||
required mmgen keys into the bitcoind wallet.dat and use the
|
||||
'--force-wallet-dat' option.
|
||||
using the '--keys-from-file' option. Alternatively, one may get keys from
|
||||
a running bitcoind using the '--force-wallet-dat' option. First import the
|
||||
required mmgen keys using 'bitcoind importprivkey'.
|
||||
|
||||
For transaction outputs that are MMGen addresses, MMGen-to-Bitcoin address
|
||||
mappings are verified. Therefore, seed material for these addresses must
|
||||
be supplied on the command line.
|
||||
|
||||
Seed data supplied in files must have the following extensions:
|
||||
wallet: '.{}'
|
||||
|
|
@ -72,78 +77,84 @@ Seed data supplied in files must have the following extensions:
|
|||
""".format(g.seed_ext,g.wallet_ext,g.seed_ext,g.mn_ext,g.brain_ext)
|
||||
}
|
||||
|
||||
short_opts = "hd:eiIk:P:qb:msw"
|
||||
short_opts = "hd:eiIk:P:qVb:msw"
|
||||
long_opts = "help","outdir=","echo_passphrase","info","tx_id",\
|
||||
"keys_from_file=","passwd_file=","quiet","from_brain=",\
|
||||
"from_mnemonic","from_seed","use_wallet_dat"
|
||||
"keys_from_file=","passwd_file=","quiet","skip_key_preverify",\
|
||||
"from_brain=","from_mnemonic","from_seed","use_wallet_dat"
|
||||
|
||||
opts,infiles = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
if "quiet" in opts: g.quiet = True
|
||||
check_opts(opts,long_opts)
|
||||
|
||||
if not infiles: usage(help_data)
|
||||
for i in infiles: check_infile(i)
|
||||
|
||||
# Begin execution
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
tx_file = infiles.pop(0)
|
||||
m = "" if 'tx_id' in opts else "transaction data"
|
||||
tx_data = get_lines_from_file(tx_file,m)
|
||||
|
||||
metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
|
||||
metadata,tx_hex,inputs_data,b2m_map = parse_tx_data(tx_data,tx_file)
|
||||
|
||||
if 'tx_id' in opts:
|
||||
msg(metadata[0])
|
||||
sys.exit(0)
|
||||
|
||||
if 'info' in opts:
|
||||
view_tx_data(c,inputs_data,tx_hex,metadata)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata)
|
||||
sys.exit(0)
|
||||
|
||||
if not 'quiet' in opts: do_license_msg(immed=True)
|
||||
do_license_msg(immed=True)
|
||||
|
||||
msg("Successfully opened transaction file '%s'" % tx_file)
|
||||
# Are inputs mmgen addresses?
|
||||
mmgen_addrs = [i for i in inputs_data if parse_mmgen_label(i['account'])[0]]
|
||||
other_addrs = [i for i in inputs_data if not parse_mmgen_label(i['account'])[0]]
|
||||
|
||||
keys = get_lines_from_file(opts['keys_from_file'],"key data",remove_comments=True) \
|
||||
if 'keys_from_file' in opts else []
|
||||
|
||||
if other_addrs and not keys and not 'use_wallet_dat' in opts:
|
||||
missing_keys_errormsg(other_addrs)
|
||||
sys.exit(2)
|
||||
|
||||
if other_addrs and keys and not 'skip_key_preverify' in opts:
|
||||
a = [i['address'] for i in other_addrs]
|
||||
preverify_keys(a, keys)
|
||||
|
||||
seeds = {}
|
||||
check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts)
|
||||
|
||||
qmsg("Successfully opened transaction file '%s'" % tx_file)
|
||||
|
||||
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":
|
||||
p = True if reply in "Vv" else False
|
||||
view_tx_data(c,inputs_data,tx_hex,metadata,pager=p)
|
||||
view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata,pager=p)
|
||||
|
||||
# Are inputs mmgen addresses?
|
||||
mmgen_addrs = [i for i in inputs_data if verify_mmgen_label(i['account'])]
|
||||
other_addrs = [i for i in inputs_data if not verify_mmgen_label(i['account'])]
|
||||
|
||||
keys = get_lines_from_file(opts['keys_from_file'],"key data") \
|
||||
if 'keys_from_file' in opts else []
|
||||
sig_data = [
|
||||
{"txid":i['txid'],"vout":i['vout'],"scriptPubKey":i['scriptPubKey']}
|
||||
for i in inputs_data]
|
||||
|
||||
if mmgen_addrs:
|
||||
if other_addrs and not keys and not 'use_wallet_dat' in opts:
|
||||
missing_keys_errormsg(other_addrs)
|
||||
sys.exit(2)
|
||||
|
||||
keys += get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts)
|
||||
ml = [i['account'].split()[0] for i in mmgen_addrs]
|
||||
keys += get_keys_for_mmgen_addrs(ml,infiles,seeds,opts)
|
||||
|
||||
if 'use_wallet_dat' in opts:
|
||||
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
|
||||
else:
|
||||
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
|
||||
elif other_addrs:
|
||||
if 'use_wallet_dat' in opts:
|
||||
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
|
||||
if keys:
|
||||
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
|
||||
else:
|
||||
if keys:
|
||||
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
|
||||
else:
|
||||
missing_keys_errormsg(other_addrs)
|
||||
sys.exit(2)
|
||||
sig_tx = sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts)
|
||||
|
||||
if sig_tx['complete']:
|
||||
msg("Signing completed")
|
||||
prompt = "Save signed transaction?"
|
||||
prompt = "OK\nSave signed transaction?"
|
||||
if user_confirm(prompt,default_yes=True):
|
||||
print_signed_tx_to_file(tx_hex,sig_tx['hex'],metadata,opts)
|
||||
else:
|
||||
msg("Some keys were missing. Transaction could not be signed.")
|
||||
msg("failed\nSome keys were missing. Transaction could not be signed.")
|
||||
sys.exit(3)
|
||||
|
|
|
|||
|
|
@ -37,17 +37,20 @@ help_data = {
|
|||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
-m, --export-mnemonic Export the wallet's mnemonic to file
|
||||
-P, --passwd-file f Get passphrase from file 'f'
|
||||
-q, --quiet Suppress warnings; overwrite files without prompting
|
||||
-s, --export-seed Export the wallet's seed to file
|
||||
-S, --stdout Print seed or mnemonic data to standard output
|
||||
-v, --verbose Produce more verbose output
|
||||
"""
|
||||
}
|
||||
|
||||
short_opts = "hd:emP:sSv"
|
||||
short_opts = "hd:emP:qsSv"
|
||||
long_opts = "help","outdir=","echo_passphrase","export_mnemonic",\
|
||||
"passwd_file=","export_seed","stdout","verbose"
|
||||
"passwd_file=","quiet","export_seed","stdout","verbose"
|
||||
|
||||
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
|
||||
|
||||
# Argument sanity checks and processing:
|
||||
check_opts(opts,long_opts)
|
||||
|
|
@ -57,12 +60,12 @@ if len(cmd_args) != 1: usage(help_data)
|
|||
check_infile(cmd_args[0])
|
||||
|
||||
if 'export_mnemonic' in opts:
|
||||
msg("Exporting mnemonic data to file by user request")
|
||||
qmsg("Exporting mnemonic data to file by user request")
|
||||
if 'export_seed' in opts:
|
||||
msg("Exporting seed data to file by user request")
|
||||
qmsg("Exporting seed data to file by user request")
|
||||
|
||||
seed = get_seed_from_wallet(cmd_args[0], opts)
|
||||
if seed: msg("Wallet is OK")
|
||||
if seed: qmsg("Wallet is OK")
|
||||
|
||||
if 'export_mnemonic' in opts:
|
||||
wl = get_default_wordlist()
|
||||
|
|
|
|||
|
|
@ -45,10 +45,11 @@ help_data = {
|
|||
-p, --hash-preset p Use scrypt.hash() parameters from preset 'p'
|
||||
(default: '{}')
|
||||
-P, --passwd-file f Get passphrase from file 'f'
|
||||
-q, --quiet Suppress warnings; overwrite files without
|
||||
-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
|
||||
|
|
@ -92,13 +93,14 @@ in all future invocations with that passphrase.
|
|||
)
|
||||
}
|
||||
|
||||
short_opts = "hd:eHl:L:p:P:qu:b:ms"
|
||||
short_opts = "hd:eHl:L:p:P:qu:vb:ms"
|
||||
long_opts = "help","outdir=","echo_passphrase","show_hash_presets","seed_len=",\
|
||||
"label=","hash_preset=","passwd_file=","quiet","usr_randlen=",\
|
||||
"from_brain=","from_mnemonic","from_seed"
|
||||
"verbose","from_brain=","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)
|
||||
|
|
@ -108,15 +110,23 @@ 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.seed_ext, g.mn_ext, g.brain_ext
|
||||
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
|
||||
|
||||
if not 'quiet' in opts: do_license_msg()
|
||||
do_license_msg()
|
||||
|
||||
msg_r("Acquiring random data from your computer...")
|
||||
qmsg_r("Acquiring random data from your computer...")
|
||||
|
||||
from time import sleep
|
||||
sleep(0.25)
|
||||
|
|
@ -129,12 +139,12 @@ except:
|
|||
msg("FAILED\nUnable to generate random numbers. Exiting")
|
||||
sys.exit(2)
|
||||
|
||||
msg("OK")
|
||||
qmsg("OK")
|
||||
|
||||
if g.debug: display_os_random_data(os_rand_data)
|
||||
|
||||
usr_keys,key_timings = get_random_data_from_user(opts)
|
||||
msg("")
|
||||
qmsg("")
|
||||
|
||||
if g.debug: display_user_random_data(usr_keys,key_timings)
|
||||
|
||||
|
|
@ -143,7 +153,8 @@ usr_rand_data = sha256(usr_keys).digest() + \
|
|||
|
||||
for i in 'from_mnemonic','from_brain','from_seed':
|
||||
if infile or (i in opts):
|
||||
seed = get_seed_retry(infile,opts); break
|
||||
seed = get_seed_retry(infile,opts)
|
||||
qmsg(""); break
|
||||
else:
|
||||
# Truncate random data for smaller seed lengths
|
||||
seed = os_rand_data[0] + usr_rand_data
|
||||
|
|
@ -152,9 +163,7 @@ else:
|
|||
salt = os_rand_data[1] + usr_rand_data
|
||||
salt = sha256(salt).digest()[:g.salt_len]
|
||||
|
||||
if not 'quiet' in opts:
|
||||
msg("""
|
||||
Now you must choose a passphrase to encrypt the seed with. A key will be
|
||||
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.
|
||||
|
|
@ -164,6 +173,6 @@ 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, opts)
|
||||
enc_seed = encrypt_seed(seed, key)
|
||||
|
||||
write_wallet_to_file(seed,passwd,make_chksum_8(key),salt,enc_seed,opts)
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import sys, getopt
|
||||
import mmgen.config as g
|
||||
from mmgen.util import msg
|
||||
from mmgen.util import msg,check_infile
|
||||
|
||||
def usage(hd):
|
||||
print "USAGE: %s %s" % (hd['prog_name'], hd['usage'])
|
||||
|
|
@ -117,7 +117,9 @@ def check_opts(opts,long_opts):
|
|||
|
||||
for ch in list(label):
|
||||
if ch not in g.wallet_label_symbols:
|
||||
msg("'%s': illegal character in label" % ch)
|
||||
msg("""
|
||||
"%s": illegal character in label. Only ASCII characters are permitted.
|
||||
""".strip() % ch)
|
||||
sys.exit(1)
|
||||
elif opt == 'from_brain':
|
||||
try:
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from hashlib import sha256, sha512
|
|||
from binascii import hexlify, unhexlify
|
||||
|
||||
from mmgen.bitcoin import numtowif
|
||||
from mmgen.util import msg,qmsg,qmsg_r
|
||||
import mmgen.config as g
|
||||
|
||||
def test_for_keyconv():
|
||||
|
|
@ -47,27 +48,12 @@ faster address generation.
|
|||
|
||||
|
||||
def generate_addrs(seed, addrnums, opts):
|
||||
"""
|
||||
generate_addresses(start, end, seed, opts) => None
|
||||
|
||||
Generate a Bitcoin address or addresses end based on a seed, optionally
|
||||
outputting secret keys
|
||||
|
||||
The 'keyconv' utility will be used for address generation if installed.
|
||||
Otherwise an internal function is used
|
||||
|
||||
Supported options:
|
||||
print_secret, no_addresses, no_keyconv, gen_what
|
||||
|
||||
Addresses are returned in a list of dictionaries with the following keys:
|
||||
num, sec, wif, addr
|
||||
"""
|
||||
|
||||
if not 'no_addresses' in opts:
|
||||
if 'no_keyconv' in opts or test_for_keyconv() == False:
|
||||
sys.stderr.write("Using (slow) internal ECDSA library for address generation\n")
|
||||
msg("Using (slow) internal ECDSA library for address generation")
|
||||
from mmgen.bitcoin import privnum2addr
|
||||
keyconv = ""
|
||||
keyconv = False
|
||||
else:
|
||||
from subprocess import Popen, PIPE
|
||||
keyconv = "keyconv"
|
||||
|
|
@ -75,12 +61,13 @@ def generate_addrs(seed, addrnums, opts):
|
|||
a,t_addrs,i,out = sorted(addrnums),len(addrnums),0,[]
|
||||
|
||||
while a:
|
||||
seed = sha512(seed).digest(); i += 1 # round /i/
|
||||
seed = sha512(seed).digest()
|
||||
i += 1 # round /i/
|
||||
if i < a[0]: continue
|
||||
|
||||
a.pop(0)
|
||||
|
||||
sys.stderr.write("\rGenerating %s %s (%s of %s)" %
|
||||
qmsg_r("\rGenerating %s %s (%s of %s)" %
|
||||
(opts['gen_what'], i, t_addrs-len(a), t_addrs))
|
||||
|
||||
# Secret key is double sha256 of seed hash round /i/
|
||||
|
|
@ -110,7 +97,7 @@ def generate_addrs(seed, addrnums, opts):
|
|||
import re
|
||||
w = re.sub('e*s$','',w)
|
||||
|
||||
sys.stderr.write("\rGenerated %s %s%s\n"%(t_addrs, w, " "*15))
|
||||
qmsg("\rGenerated %s %s%s"%(t_addrs, w, " "*15))
|
||||
|
||||
return out
|
||||
|
||||
|
|
@ -153,10 +140,9 @@ def format_addr_data(addrlist, seed_chksum, opts):
|
|||
# Everything following a hash symbol '#' is a comment and ignored by {}.
|
||||
# A text label of {} characters or less may be added to the right of each
|
||||
# address, and it will be appended to the bitcoind wallet label upon import.
|
||||
# The label may contain ASCII letters, numerals, and the symbols
|
||||
# '{}' and '{}'.
|
||||
""".format(g.proj_name.capitalize(),g.max_addr_label_len,
|
||||
"', '".join(g.addr_label_punc[0:-1]), g.addr_label_punc[-1]).strip()
|
||||
# The label may contain printable ASCII symbols.
|
||||
""".strip().format(g.proj_name_cap,g.max_addr_label_len)
|
||||
|
||||
data = []
|
||||
if not 'stdout' in opts: data.append(header + "\n")
|
||||
data.append("%s {" % seed_chksum.upper())
|
||||
|
|
@ -210,7 +196,7 @@ def write_addr_data_to_file(seed, data, addr_list, opts):
|
|||
else: ext = "akeys"
|
||||
|
||||
if 'b16' in opts: ext = ext.replace("keys","xkeys")
|
||||
from mmgen.util import write_to_file, make_chksum_8, msg
|
||||
from mmgen.util import write_to_file, make_chksum_8
|
||||
outfile = "{}[{}].{}".format(
|
||||
make_chksum_8(seed),
|
||||
fmt_addr_list(addr_list),
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
#!/usr/bin/env python
|
||||
#
|
||||
# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
|
||||
# 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
|
||||
|
|
@ -61,15 +61,10 @@ def pubhex2addr(pubhex):
|
|||
pubkey = int(step2 + step4[:8], 16)
|
||||
return "1" + ("1" * extra_ones) + _numtob58(pubkey)
|
||||
|
||||
def privnum2addr(numpriv):
|
||||
pko = ecdsa.SigningKey.from_secret_exponent(numpriv,secp256k1)
|
||||
pubkey = hexlify(pko.get_verifying_key().to_string())
|
||||
return pubhex2addr('04'+pubkey)
|
||||
|
||||
def verify_addr(addr):
|
||||
def verify_addr(addr,verbose=False):
|
||||
|
||||
if addr[0] != "1":
|
||||
print "%s: Invalid address" % addr
|
||||
if verbose: print "%s: Invalid address" % addr
|
||||
return False
|
||||
|
||||
num = _b58tonum(addr[1:])
|
||||
|
|
@ -80,7 +75,7 @@ def verify_addr(addr):
|
|||
step2 = sha256(step1).hexdigest()
|
||||
|
||||
if step2[:8] != addr_hex[40:]:
|
||||
print "Invalid checksum in address %s" % ("1" + addr)
|
||||
if verbose: print "Invalid checksum in address %s" % ("1" + addr)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
|
@ -115,8 +110,7 @@ def numtowif(numpriv):
|
|||
key = step1 + step3[:8]
|
||||
return _numtob58(int(key,16))
|
||||
|
||||
|
||||
# The following are mmgen internal (non-bitcoin) b58 functions
|
||||
# The following are MMGen internal (non-Bitcoin) b58 functions
|
||||
|
||||
# Drop-in replacements for b64encode() and b64decode():
|
||||
# (well, not exactly: they yield numeric but not bytewise equivalence)
|
||||
|
|
@ -159,19 +153,36 @@ def b58decode_pad(s):
|
|||
return _b58_pad(s,
|
||||
a=b58_lens,b=bin_lens,pad='\0',f=b58decode,w="base 58 numbers")
|
||||
|
||||
# Compressed address support:
|
||||
|
||||
################### FUNCTIONS UNUSED BY MMGEN: ###################
|
||||
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')
|
||||
if compressed and key[66:68] != '01': return False
|
||||
round1 = sha256(unhexlify(key[:idx])).digest()
|
||||
round2 = sha256(round1).hexdigest()
|
||||
return key[2:66] if (key[:2] == '80' and key[idx:] == round2[:8]) else False
|
||||
|
||||
# To check validity, recode with numtowif()
|
||||
def hextowif(hexpriv,compressed=False):
|
||||
step1 = '80' + hexpriv + ('01' if compressed else '')
|
||||
step2 = sha256(unhexlify(step1)).digest()
|
||||
step3 = sha256(step2).hexdigest()
|
||||
key = step1 + step3[:8]
|
||||
return _numtob58(int(key,16))
|
||||
|
||||
def privnum2addr(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'
|
||||
return pubhex2addr(p+pubkey[:64])
|
||||
else:
|
||||
return pubhex2addr('04'+pubkey)
|
||||
|
||||
# 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
|
||||
|
||||
def wiftohex(wifpriv):
|
||||
num = _b58tonum(wifpriv)
|
||||
if num == False: return False
|
||||
key = hex(num)[2:].rstrip('L')
|
||||
round1 = sha256(unhexlify(key[:66])).digest()
|
||||
round2 = sha256(round1).hexdigest()
|
||||
return key[2:66] if (key[:2] == '80' and key[66:] == round2[:8]) else False
|
||||
|
|
|
|||
|
|
@ -18,12 +18,15 @@
|
|||
"""
|
||||
config.py: Constants and configuration options for the mmgen suite
|
||||
"""
|
||||
quiet,verbose = False,False
|
||||
min_screen_width = 80
|
||||
|
||||
from decimal import Decimal
|
||||
tx_fee = Decimal("0.001")
|
||||
max_tx_fee = Decimal("0.1")
|
||||
|
||||
proj_name = "mmgen"
|
||||
proj_name_cap = "MMGen"
|
||||
|
||||
wallet_ext = "mmdat"
|
||||
seed_ext = "mmseed"
|
||||
|
|
@ -71,12 +74,16 @@ hash_presets = {
|
|||
'6': [17, 8, 20],
|
||||
}
|
||||
|
||||
mmgen_idx_max_digits = 7
|
||||
|
||||
from string import ascii_letters, digits
|
||||
|
||||
addr_label_punc = ".","_",",","-"," "
|
||||
addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
|
||||
max_addr_label_len = 16
|
||||
addr_label_symbols = tuple([chr(i) for i in range(0x20,0x7f)])
|
||||
max_addr_label_len = 32
|
||||
|
||||
wallet_label_punc = ".", "_", " "
|
||||
wallet_label_symbols = tuple(ascii_letters + digits) + wallet_label_punc
|
||||
wallet_label_symbols = addr_label_symbols
|
||||
max_wallet_label_len = 32
|
||||
|
||||
#addr_label_punc = ".","_",",","-"," ","(",")"
|
||||
#addr_label_symbols = tuple(ascii_letters + digits) + addr_label_punc
|
||||
#wallet_label_punc = addr_label_punc
|
||||
|
|
|
|||
|
|
@ -585,6 +585,10 @@ copy of the Program in return for a fee.
|
|||
}
|
||||
|
||||
def do_license_msg(immed=False):
|
||||
|
||||
import mmgen.config as g
|
||||
if g.quiet: return
|
||||
|
||||
msg(gpl['warning'])
|
||||
prompt = "%s " % gpl['prompt'].strip()
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ class AuthServiceProxy(object):
|
|||
except:
|
||||
from mmgen.util import msg
|
||||
import sys
|
||||
msg("\nUnable to connect to bitcoind.")
|
||||
msg("Unable to connect to bitcoind")
|
||||
sys.exit(2)
|
||||
|
||||
httpresp = self.__conn.getresponse()
|
||||
|
|
|
|||
191
mmgen/term.py
Executable file
191
mmgen/term.py
Executable file
|
|
@ -0,0 +1,191 @@
|
|||
#!/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/>.
|
||||
"""
|
||||
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)
|
||||
|
||||
def _kb_hold_protect_unix():
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
tty.setcbreak(fd)
|
||||
|
||||
timeout = float(0.3)
|
||||
|
||||
try:
|
||||
while True:
|
||||
key = select([sys.stdin], [], [], timeout)[0]
|
||||
if key: sys.stdin.read(1)
|
||||
else: break
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
|
||||
def _get_keypress_unix(prompt="",immed_chars=""):
|
||||
|
||||
msg_r(prompt)
|
||||
timeout = float(0.3)
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
tty.setcbreak(fd)
|
||||
|
||||
try:
|
||||
while True:
|
||||
select([sys.stdin], [], [], False)
|
||||
ch = sys.stdin.read(1)
|
||||
if immed_chars == "ALL" or ch in immed_chars:
|
||||
return ch
|
||||
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
|
||||
return ch
|
||||
second_key = select([sys.stdin], [], [], timeout)[0]
|
||||
if second_key: continue
|
||||
else: return ch
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
|
||||
def _kb_hold_protect_mswin():
|
||||
|
||||
timeout = float(0.5)
|
||||
|
||||
try:
|
||||
while True:
|
||||
hit_time = time.time()
|
||||
while True:
|
||||
if msvcrt.kbhit():
|
||||
msvcrt.getch()
|
||||
break
|
||||
if float(time.time() - hit_time) > timeout:
|
||||
return
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def _get_keypress_mswin(prompt="",immed_chars=""):
|
||||
|
||||
msg_r(prompt)
|
||||
timeout = float(0.5)
|
||||
|
||||
try:
|
||||
while True:
|
||||
if msvcrt.kbhit():
|
||||
ch = msvcrt.getch()
|
||||
|
||||
if ord(ch) == 3: raise KeyboardInterrupt
|
||||
|
||||
if immed_chars == "ALL" or ch in immed_chars:
|
||||
return ch
|
||||
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
|
||||
return ch
|
||||
|
||||
hit_time = time.time()
|
||||
|
||||
while True:
|
||||
if msvcrt.kbhit(): break
|
||||
if float(time.time() - hit_time) > timeout:
|
||||
return ch
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
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:
|
||||
pass
|
||||
|
||||
cr = ioctl_GWINSZ(0) or ioctl_GWINSZ(1) or ioctl_GWINSZ(2)
|
||||
|
||||
if not cr:
|
||||
try:
|
||||
fd = os.open(os.ctermid(), os.O_RDONLY)
|
||||
cr = ioctl_GWINSZ(fd)
|
||||
os.close(fd)
|
||||
except:
|
||||
pass
|
||||
|
||||
if not cr:
|
||||
try:
|
||||
cr = (os.environ['LINES'], os.environ['COLUMNS'])
|
||||
except:
|
||||
return 80,25
|
||||
|
||||
return int(cr[1]), int(cr[0])
|
||||
|
||||
|
||||
def _get_terminal_size_mswin():
|
||||
try:
|
||||
from ctypes import windll, create_string_buffer
|
||||
# stdin handle is -10
|
||||
# stdout handle is -11
|
||||
# stderr handle is -12
|
||||
h = windll.kernel32.GetStdHandle(-12)
|
||||
csbi = create_string_buffer(22)
|
||||
res = windll.kernel32.GetConsoleScreenBufferInfo(h, csbi)
|
||||
if res:
|
||||
(bufx, bufy, curx, cury, wattr,
|
||||
left, top, right, bottom,
|
||||
maxx, maxy) = struct.unpack("hhhhHhhhhhh", csbi.raw)
|
||||
sizex = right - left + 1
|
||||
sizey = bottom - top + 1
|
||||
return sizex, sizey
|
||||
except:
|
||||
return 80,25
|
||||
|
||||
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
|
||||
except:
|
||||
try:
|
||||
import msvcrt, time
|
||||
get_char = _get_keypress_mswin
|
||||
kb_hold_protect = _kb_hold_protect_mswin
|
||||
get_terminal_size = _get_terminal_size_mswin
|
||||
except:
|
||||
if not sys.platform.startswith("linux") \
|
||||
and not sys.platform.startswith("win"):
|
||||
msg("Unsupported platform: %s" % sys.platform)
|
||||
msg("This program currently runs only on Linux and Windows")
|
||||
else:
|
||||
msg("Unable to set terminal mode")
|
||||
sys.exit(2)
|
||||
|
||||
if __name__ == "__main__":
|
||||
print "columns: {}, rows: {}".format(*get_terminal_size())
|
||||
|
|
@ -19,18 +19,19 @@
|
|||
bitcoin.py: Test suite for mmgen.bitcoin module
|
||||
"""
|
||||
|
||||
from mmgen.bitcoin import *
|
||||
from mmgen.util import msg
|
||||
from test import *
|
||||
import mmgen.bitcoin as b
|
||||
from mmgen.util import msg
|
||||
from mmgen.tests.test import *
|
||||
from binascii import hexlify, unhexlify
|
||||
|
||||
import sys
|
||||
|
||||
def b58_randenc():
|
||||
r = get_random(24)
|
||||
r_enc = b58encode(r)
|
||||
r_enc = b.b58encode(r)
|
||||
print "Data (hex): %s" % hexlify(r)
|
||||
print "Base 58: %s" % r_enc
|
||||
r_dec = b58decode(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"
|
||||
|
|
@ -55,9 +56,8 @@ def keyconv_compare_randloop(loops, quiet=False):
|
|||
else:
|
||||
print "%s iterations completed" % i
|
||||
|
||||
except:
|
||||
print "\nUser interrupt"
|
||||
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
|
||||
def keyconv_compare(wif,quiet=False):
|
||||
do_msg = nomsg if quiet else msg
|
||||
|
|
@ -69,7 +69,7 @@ def keyconv_compare(wif,quiet=False):
|
|||
print "Error with execution of keyconv"
|
||||
sys.exit(3)
|
||||
kc_addr = dict([j.split() for j in p.stdout.readlines()])['Address:']
|
||||
addr = privnum2addr(wiftonum(wif))
|
||||
addr = b.privnum2addr(b.wiftonum(wif))
|
||||
do_msg("Address (mmgen): %s" % addr)
|
||||
do_msg("Address (keyconv): %s" % kc_addr)
|
||||
if (kc_addr != addr):
|
||||
|
|
@ -113,13 +113,13 @@ def numtowif_rand(quiet=False):
|
|||
|
||||
def strtob58(s,quiet=False):
|
||||
print "Input: %s" % s
|
||||
s_enc = b58encode(s)
|
||||
s_enc = b.b58encode(s)
|
||||
print "Encoded data: %s" % s_enc
|
||||
s_dec = b58decode(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=b58encode, f_dec=b58decode, quiet=False):
|
||||
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)
|
||||
|
|
@ -129,7 +129,7 @@ def hextob58(s_in,f_enc=b58encode, f_dec=b58decode, quiet=False):
|
|||
do_msg("Recoded data: %s" % s_dec)
|
||||
test_equality(s_in,s_dec,["0"],quiet)
|
||||
|
||||
def b58tohex(s_in,f_dec=b58decode, f_enc=b58encode,quiet=False):
|
||||
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)
|
||||
|
|
@ -138,25 +138,25 @@ def b58tohex(s_in,f_dec=b58decode, f_enc=b58encode,quiet=False):
|
|||
test_equality(s_in,s_enc,["1"],quiet)
|
||||
|
||||
def hextob58_pad(s_in, quiet=False):
|
||||
hextob58(s_in,f_enc=b58encode_pad, f_dec=b58decode_pad, quiet=quiet)
|
||||
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=b58decode_pad, f_enc=b58encode_pad, quiet=quiet)
|
||||
b58tohex(s_in,f_dec=b.b58decode_pad, f_enc=b.b58encode_pad, quiet=quiet)
|
||||
|
||||
def hextob58_pad_randloop(loops, quiet=False):
|
||||
try:
|
||||
for i in range(1,int(loops)+1):
|
||||
r = hexlify(get_random(32))
|
||||
hextob58(r,f_enc=b58encode_pad, f_dec=b58decode_pad, quiet=quiet)
|
||||
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)
|
||||
except:
|
||||
print "User interrupt"
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
|
||||
def test_wiftohex(s_in,f_dec=wiftohex,f_enc=numtowif):
|
||||
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
|
||||
|
|
@ -170,7 +170,31 @@ def hextosha256(s_in):
|
|||
|
||||
def pubhextoaddr(s_in):
|
||||
print "Entered data: %s" % s_in
|
||||
s_enc = pubhex2addr(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 = {
|
||||
|
|
@ -188,6 +212,10 @@ tests = {
|
|||
"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]'],
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ test.py: Shared routines for mmgen test suite
|
|||
"""
|
||||
|
||||
import sys
|
||||
from mmgen.util import msg
|
||||
|
||||
def msg(s): print s
|
||||
def nomsg(s): pass
|
||||
|
||||
def test_equality(num_in,num_out,wl,quiet=False):
|
||||
|
|
|
|||
550
mmgen/tx.py
550
mmgen/tx.py
|
|
@ -29,9 +29,7 @@ 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. Total inputs - transaction fee = %s BTC.
|
||||
To create a valid transaction with no change address, send this sum to the
|
||||
specified recipient address.
|
||||
address was specified.
|
||||
""".strip(),
|
||||
'mixed_inputs': """
|
||||
NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs,
|
||||
|
|
@ -73,30 +71,36 @@ def trim_exponent(n):
|
|||
return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
|
||||
|
||||
|
||||
def check_address(rcpt_address):
|
||||
from mmgen.bitcoin import verify_addr
|
||||
if not verify_addr(rcpt_address):
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def check_btc_amt(send_amt):
|
||||
def is_btc_amt(amt):
|
||||
|
||||
from decimal import Decimal
|
||||
try:
|
||||
retval = Decimal(send_amt)
|
||||
ret = Decimal(amt)
|
||||
except:
|
||||
msg("%s: Invalid amount" % send_amt)
|
||||
sys.exit(3)
|
||||
msg("%s: Invalid amount" % amt)
|
||||
return False
|
||||
|
||||
if g.debug:
|
||||
print "Decimal(amt): %s\nAs tuple: %s" % (send_amt,repr(retval.as_tuple()))
|
||||
print "Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple()))
|
||||
|
||||
if retval.as_tuple()[-1] < -8:
|
||||
msg("%s: Too many decimal places in amount" % send_amt)
|
||||
if ret.as_tuple()[-1] < -8:
|
||||
msg("%s: Too many decimal places in amount" % amt)
|
||||
return False
|
||||
|
||||
if ret == 0:
|
||||
msg("Requested zero BTC amount")
|
||||
return False
|
||||
|
||||
return trim_exponent(ret)
|
||||
|
||||
def check_btc_amt(amt):
|
||||
ret = is_btc_amt(amt)
|
||||
if ret:
|
||||
return ret
|
||||
else:
|
||||
sys.exit(3)
|
||||
|
||||
return trim_exponent(retval)
|
||||
|
||||
|
||||
def get_bitcoind_cfg_options(cfg_keys):
|
||||
|
||||
|
|
@ -108,7 +112,7 @@ def get_bitcoind_cfg_options(cfg_keys):
|
|||
data_dir = r"Application Data\Bitcoin"
|
||||
cfg_file = "%s\%s\%s" % (os.environ["HOMEPATH"],data_dir,"bitcoin.conf")
|
||||
else:
|
||||
msg("Neither $HOME nor %HOMEPATH% is set")
|
||||
msg("Neither $HOME nor %HOMEPATH% are set")
|
||||
msg("Don't know where to look for 'bitcoin.conf'")
|
||||
sys.exit(3)
|
||||
|
||||
|
|
@ -135,17 +139,16 @@ def get_bitcoind_cfg_options(cfg_keys):
|
|||
return cfg
|
||||
|
||||
|
||||
def print_tx_to_file(tx,sel_unspent,send_amt,opts):
|
||||
def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
|
||||
tx_id = make_chksum_6(unhexlify(tx)).upper()
|
||||
outfile = "tx_%s[%s].raw" % (tx_id,send_amt)
|
||||
if 'outdir' in opts:
|
||||
outfile = "%s/%s" % (opts['outdir'], outfile)
|
||||
metadata = "%s %s %s" % (tx_id, send_amt, make_timestamp())
|
||||
sig_data = [{"txid":i.txid,"vout":i.vout,"scriptPubKey":i.scriptPubKey}
|
||||
for i in sel_unspent]
|
||||
data = "%s\n%s\n%s\n%s\n" % (
|
||||
metadata, tx, repr(sig_data),
|
||||
repr([i.__dict__ for i in sel_unspent])
|
||||
metadata, tx,
|
||||
repr([i.__dict__ for i in sel_unspent]),
|
||||
repr(b2m_map)
|
||||
)
|
||||
write_to_file(outfile,data,confirm=False)
|
||||
msg("Transaction data saved to file '%s'" % outfile)
|
||||
|
|
@ -176,17 +179,11 @@ def format_unspent_outputs_for_printing(out,sort_info,total):
|
|||
"Amount (BTC)","Age (days)", "Comment")]
|
||||
|
||||
for n,i in enumerate(out):
|
||||
if verify_mmgen_label(i.account):
|
||||
s = i.account.split(None,1)
|
||||
mmid,cmt = s[0],(s[1] if len(s) == 2 else "")
|
||||
else:
|
||||
mmid,cmt = "",i.account
|
||||
|
||||
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,mmid,i.amt,i.days,cmt)
|
||||
s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,i.mmid,i.amt,i.days,i.label)
|
||||
pout.append(s.rstrip())
|
||||
|
||||
return \
|
||||
|
|
@ -197,23 +194,20 @@ def format_unspent_outputs_for_printing(out,sort_info,total):
|
|||
|
||||
def sort_and_view(unspent):
|
||||
|
||||
def s_amt(a,b): return cmp(a.amount,b.amount)
|
||||
def s_txid(a,b):
|
||||
return cmp("%s %03s" % (a.txid,a.vout), "%s %03s" % (b.txid,b.vout))
|
||||
def s_addr(a,b): return cmp(a.address,b.address)
|
||||
def s_age(a,b): return cmp(b.confirmations,a.confirmations)
|
||||
def s_mmgen(a,b): return cmp(a.account,b.account)
|
||||
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): return i.account
|
||||
|
||||
fs = " %-4s %-11s %-2s %-34s %-13s %-s"
|
||||
sort,group,show_mmaddr,reverse = "",False,False,False
|
||||
total = trim_exponent(sum([i.amount for i in unspent]))
|
||||
|
||||
hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s"
|
||||
table_hdr = fs % ("Num","TX id Vout","","Address","Amount (BTC)", "Age(days)")
|
||||
|
||||
options_msg = """
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
|
||||
Format options: [g]roup, show [m]mgen addr
|
||||
Display options: [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): "
|
||||
|
|
@ -222,57 +216,82 @@ Format options: [g]roup, show [m]mgen addr
|
|||
print_to_file_msg = ""
|
||||
msg("")
|
||||
|
||||
from mmgen.term import get_terminal_size
|
||||
|
||||
max_acct_len = max([len(i.account) for i in unspent])
|
||||
|
||||
while True:
|
||||
out = deepcopy(unspent)
|
||||
for i in out: i.skip = ""
|
||||
cols = get_terminal_size()[0]
|
||||
if cols < g.min_screen_width:
|
||||
msg("mmgen-txcreate requires a screen at least %s characters wide" %
|
||||
g.min_screen_width)
|
||||
sys.exit(2)
|
||||
|
||||
addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
|
||||
tx_w = max(11,min(64, cols-addr_w-32))
|
||||
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)")
|
||||
|
||||
unsp = deepcopy(unspent)
|
||||
for i in unsp: i.skip = ""
|
||||
if group and (sort == "address" or sort == "txid"):
|
||||
for n in range(len(out)-1):
|
||||
a,b = out[n],out[n+1]
|
||||
for n in range(len(unsp)-1):
|
||||
a,b = unsp[n],unsp[n+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 out:
|
||||
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.mmid,i.label = parse_mmgen_label(i.account)
|
||||
|
||||
if i.skip == "addr":
|
||||
i.addr = "|" + "." * 33
|
||||
else:
|
||||
if show_mmaddr:
|
||||
if verify_mmgen_label(i.account):
|
||||
i.addr = "%s.. %s" % (i.address[:4],i.account)
|
||||
else:
|
||||
i.addr = i.address
|
||||
if show_mmaddr and i.mmid:
|
||||
acct_w = min(max_acct_len, max(24,int(addr_w-10)))
|
||||
btaddr_w = addr_w - acct_w - 1
|
||||
|
||||
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 = " |..." if i.skip == "txid" else i.txid[:8]+"..."
|
||||
dots = "..." if tx_w < 64 else ""
|
||||
i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
|
||||
else i.txid[:tx_w-len(dots)]+dots
|
||||
|
||||
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")
|
||||
|
||||
output = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
|
||||
out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
|
||||
|
||||
for n,i in enumerate(out):
|
||||
output.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days))
|
||||
for n,i in enumerate(unsp):
|
||||
out.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days))
|
||||
|
||||
msg("\n".join(output) +"\n\n" + print_to_file_msg + options_msg)
|
||||
msg("\n".join(out) +"\n\n" + print_to_file_msg + options_msg)
|
||||
print_to_file_msg = ""
|
||||
|
||||
immed_chars = "qpPtadArMgm"
|
||||
immed_chars = "atdAMrgmeqpvw"
|
||||
skip_prompt = False
|
||||
|
||||
while True:
|
||||
reply = get_char(prompt, immed_chars=immed_chars)
|
||||
|
||||
if reply == 'a': unspent.sort(s_amt); sort = "amount"
|
||||
elif reply == 't': unspent.sort(s_txid); sort = "txid"
|
||||
elif reply == 'd': unspent.sort(s_addr); sort = "address"
|
||||
elif reply == 'A': unspent.sort(s_age); sort = "age"
|
||||
if reply == 'a': unspent.sort(key=s_amt); sort = "amount"
|
||||
elif reply == 't': unspent.sort(key=s_txid); sort = "txid"
|
||||
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(s_mmgen)
|
||||
sort = "mmgen"
|
||||
|
|
@ -282,17 +301,18 @@ Format options: [g]roup, show [m]mgen addr
|
|||
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':
|
||||
data = format_unspent_outputs_for_printing(out,sort_info,total)
|
||||
data = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
outfile = "listunspent[%s].out" % ",".join(sort_info)
|
||||
write_to_file(outfile, data)
|
||||
print_to_file_msg = "Data written to '%s'\n\n" % outfile
|
||||
elif reply == 'v':
|
||||
do_pager("\n".join(output))
|
||||
do_pager("\n".join(out))
|
||||
continue
|
||||
elif reply == 'w':
|
||||
data = format_unspent_outputs_for_printing(out,sort_info,total)
|
||||
data = format_unspent_outputs_for_printing(unsp,sort_info,total)
|
||||
do_pager(data)
|
||||
continue
|
||||
else:
|
||||
|
|
@ -307,31 +327,19 @@ Format options: [g]roup, show [m]mgen addr
|
|||
return tuple(unspent)
|
||||
|
||||
|
||||
def verify_mmgen_label(s,return_str=False,check_label_len=False):
|
||||
def parse_mmgen_label(s,check_label_len=False):
|
||||
|
||||
fail = "" if return_str else False
|
||||
success = s if return_str else True
|
||||
if not s: return "",""
|
||||
|
||||
if not s: return fail
|
||||
try: w1,w2 = s.split(None,1)
|
||||
except: w1,w2 = s,""
|
||||
|
||||
try:
|
||||
mminfo,comment = s.split(None,1)
|
||||
except:
|
||||
mminfo,comment = s,None
|
||||
|
||||
if mminfo[8] != ':': return fail
|
||||
for i in mminfo[:8]:
|
||||
if not i in "01234567890ABCDEF": return fail
|
||||
for i in mminfo[9:]:
|
||||
if not i in "0123456789": return fail
|
||||
|
||||
if check_label_len and comment:
|
||||
check_addr_comment(comment)
|
||||
|
||||
return success
|
||||
if not is_mmgen_addr(w1): return "",w1
|
||||
if check_label_len: check_addr_label(w2)
|
||||
return w1,w2
|
||||
|
||||
|
||||
def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False):
|
||||
def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False):
|
||||
|
||||
td = c.decoderawtransaction(tx_hex)
|
||||
|
||||
|
|
@ -347,39 +355,51 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[],pager=False):
|
|||
if j['txid'] == i['txid'] and j['vout'] == i['vout']:
|
||||
days = int(j['confirmations'] * g.mins_per_block / (60*24))
|
||||
total_in += j['amount']
|
||||
out += (" " + """
|
||||
%-2s tx,vout: %s,%s
|
||||
address: %s
|
||||
ID/label: %s
|
||||
amount: %s BTC
|
||||
confirmations: %s (around %s days)
|
||||
""".strip() %
|
||||
(n+1,i['txid'],i['vout'],j['address'],verify_mmgen_label(j['account'],True),
|
||||
trim_exponent(j['amount']),j['confirmations'],days)+"\n\n")
|
||||
addr = j['address']
|
||||
|
||||
if j['account']:
|
||||
tmp = j['account'].split(None,1)
|
||||
mmid,label = tmp if len(tmp) == 2 else (tmp[0],"")
|
||||
label = label or ""
|
||||
else:
|
||||
mmid,label = "",""
|
||||
|
||||
mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
|
||||
|
||||
for d in (
|
||||
(n+1, "tx,vout:", "%s,%s" % (i['txid'], i['vout'])),
|
||||
("", "address:", addr + mmid_str),
|
||||
("", "label:", label),
|
||||
("", "amount:", "%s BTC" % trim_exponent(j['amount'])),
|
||||
("", "confirmations:", "%s (around %s days)" % (j['confirmations'], days))
|
||||
):
|
||||
if d[2]: out += ("%3s %-8s %s\n" % d)
|
||||
out += "\n"
|
||||
|
||||
break
|
||||
|
||||
out += "Total input: %s BTC\n\n" % trim_exponent(total_in)
|
||||
|
||||
total_out = 0
|
||||
out += "Outputs:\n\n"
|
||||
for n,i in enumerate(td['vout']):
|
||||
addr = i['scriptPubKey']['addresses'][0]
|
||||
mmid,label = b2m_map[addr] if addr in b2m_map else ("","")
|
||||
mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
|
||||
total_out += i['value']
|
||||
out += (" " + """
|
||||
%-2s address: %s
|
||||
amount: %s BTC
|
||||
""".strip() % (
|
||||
n,
|
||||
i['scriptPubKey']['addresses'][0],
|
||||
trim_exponent(i['value']))
|
||||
+ "\n\n")
|
||||
for d in (
|
||||
(n+1, "address:", addr + mmid_str),
|
||||
("", "label:", label),
|
||||
("", "amount:", trim_exponent(i['value']))
|
||||
):
|
||||
if d[2]: out += ("%3s %-8s %s\n" % d)
|
||||
out += "\n"
|
||||
|
||||
out += "Total input: %s BTC\n" % trim_exponent(total_in)
|
||||
out += "Total output: %s BTC\n" % trim_exponent(total_out)
|
||||
out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out)
|
||||
|
||||
if pager: do_pager(out+"\n")
|
||||
if pager: do_pager(out)
|
||||
else: msg("\n"+out)
|
||||
|
||||
|
||||
|
||||
def parse_tx_data(tx_data,infile):
|
||||
|
||||
if len(tx_data) != 4:
|
||||
|
|
@ -396,20 +416,28 @@ def parse_tx_data(tx_data,infile):
|
|||
except:
|
||||
msg(err_fmt % "hex data")
|
||||
sys.exit(2)
|
||||
else:
|
||||
if not tx_data:
|
||||
msg("Transaction is empty!")
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
sig_data = eval(tx_data[2])
|
||||
except:
|
||||
msg(err_fmt % "signature data")
|
||||
sys.exit(2)
|
||||
|
||||
try:
|
||||
inputs_data = eval(tx_data[3])
|
||||
inputs_data = eval(tx_data[2])
|
||||
except:
|
||||
msg(err_fmt % "inputs data")
|
||||
sys.exit(2)
|
||||
else:
|
||||
if not inputs_data:
|
||||
msg("Transaction has no inputs!")
|
||||
sys.exit(2)
|
||||
|
||||
return tx_data[0].split(),tx_data[1],sig_data,inputs_data
|
||||
try:
|
||||
map_data = eval(tx_data[3])
|
||||
except:
|
||||
msg(err_fmt % "mmgen to btc address map data")
|
||||
sys.exit(2)
|
||||
|
||||
return tx_data[0].split(),tx_data[1],inputs_data,map_data
|
||||
|
||||
|
||||
def select_outputs(unspent,prompt):
|
||||
|
|
@ -430,8 +458,83 @@ def select_outputs(unspent,prompt):
|
|||
|
||||
return selected
|
||||
|
||||
def is_mmgen_seed(s):
|
||||
import re
|
||||
return len(s) == 8 and re.match(r"^[0123456789ABCDEF]*$",s)
|
||||
|
||||
def mmgen_addr_to_btc_addr(m,addr_data):
|
||||
def is_mmgen_num(s):
|
||||
import re
|
||||
return len(s) <= g.mmgen_idx_max_digits \
|
||||
and re.match(r"^[123456789]+[0123456789]*$",s)
|
||||
|
||||
def is_mmgen_addr(s):
|
||||
import re
|
||||
return len(s) > 9 and s[8] == ':' \
|
||||
and re.match(r"^[0123456789ABCDEF]*$",s[:8]) \
|
||||
and len(s[9:]) <= g.mmgen_idx_max_digits \
|
||||
and re.match(r"^[123456789]+[0123456789]*$",s[9:])
|
||||
|
||||
def is_btc_addr(s):
|
||||
from mmgen.bitcoin import verify_addr
|
||||
return verify_addr(s)
|
||||
|
||||
|
||||
def btc_addr_to_mmgen_addr(btc_addr,b2m_map):
|
||||
if btc_addr in b2m_map:
|
||||
return b2m_map[btc_addr]
|
||||
return "",""
|
||||
|
||||
|
||||
def mmgen_addr_to_walletd(c,mmaddr,acct_data):
|
||||
|
||||
# We don't want to create a new object, so we'll use append()
|
||||
if not acct_data:
|
||||
for i in c.listaccounts():
|
||||
acct_data.append(i)
|
||||
|
||||
for a in acct_data:
|
||||
if not a: continue
|
||||
try:
|
||||
w1,w2 = a.split(None,1)
|
||||
except:
|
||||
w1,w2 = a,""
|
||||
if w1 == mmaddr:
|
||||
acct = a
|
||||
break
|
||||
else:
|
||||
return "",""
|
||||
|
||||
alist = c.getaddressesbyaccount(acct)
|
||||
|
||||
if len(alist) != 1:
|
||||
msg("""
|
||||
ERROR: More than one address found for account: "%s".
|
||||
The tracking "wallet.dat" file appears to have been altered by a non-%s
|
||||
program. Please restore "wallet.dat" from a backup or create a new wallet
|
||||
and re-import your addresses.
|
||||
""".strip() % (acct,g.proj_name_cap))
|
||||
sys.exit(3)
|
||||
|
||||
return alist[0],w2
|
||||
|
||||
|
||||
def mmgen_addr_to_addr_data(m,addr_data):
|
||||
|
||||
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() % m
|
||||
warn_msg = """
|
||||
Warning: no data for address '%s' exists in the wallet, so it was
|
||||
taken from the user-supplied address file. You're strongly advised to
|
||||
import this address into your tracking wallet before proceeding with
|
||||
this transaction. The address will not be tracked until you do so.
|
||||
""".strip() % m
|
||||
fail_msg = """
|
||||
No data found for MMgen address '%s' in either wallet or supplied
|
||||
address file. Please import this address into your tracking wallet, or
|
||||
supply an address file for it on the command line.
|
||||
""".strip() % m
|
||||
|
||||
ID,num = m.split(":")
|
||||
from binascii import unhexlify
|
||||
|
|
@ -442,68 +545,66 @@ def mmgen_addr_to_btc_addr(m,addr_data):
|
|||
except: pass
|
||||
else:
|
||||
if not addr_data:
|
||||
msg("Address data must be supplied for MMgen address '%s'" % m)
|
||||
msg(no_data_msg)
|
||||
sys.exit(2)
|
||||
for i in addr_data:
|
||||
if ID == i[0]:
|
||||
for j in i[1]:
|
||||
if j[0] == num:
|
||||
return j[1]
|
||||
msg("MMgen address '%s' not found in supplied address data" % m)
|
||||
msg(warn_msg)
|
||||
if not user_confirm("Continue anyway?"):
|
||||
sys.exit(1)
|
||||
return j[1],(j[2] if len(j) == 3 else "")
|
||||
msg(fail_msg)
|
||||
sys.exit(2)
|
||||
|
||||
msg("Invalid format: %s" % m)
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts):
|
||||
in_maplist = [(i['account'].split()[0],i['address'])
|
||||
for i in inputs_data if i['account']
|
||||
and is_mmgen_addr(i['account'].split()[0])]
|
||||
out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
|
||||
|
||||
def make_tx_out(tx_arg,addr_data):
|
||||
for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
|
||||
if not maplist: continue
|
||||
qmsg("Checking MMGen -> BTC address mappings for %s" % label)
|
||||
mmaddrs = [i[0] for i in maplist]
|
||||
from copy import deepcopy
|
||||
pairs = get_keys_for_mmgen_addrs(mmaddrs,
|
||||
deepcopy(infiles),seeds,opts,gen_pairs=True)
|
||||
for a,b in zip(sorted(pairs),sorted(maplist)):
|
||||
if a != b:
|
||||
msg("""
|
||||
MMGen -> BTC address mappings differ!
|
||||
In transaction: %s
|
||||
Generated from seed: %s
|
||||
""".strip() % (" ".join(a)," ".join(b)))
|
||||
sys.exit(3)
|
||||
|
||||
tx = {}
|
||||
for i in tx_arg:
|
||||
addr,amt = i.split(",")
|
||||
|
||||
if ":" in addr:
|
||||
addr = mmgen_addr_to_btc_addr(addr,addr_data)
|
||||
else:
|
||||
check_address(addr)
|
||||
|
||||
try: tx[addr] = amt
|
||||
except:
|
||||
msg("Invalid format: %s: %s" % (addr,amt))
|
||||
sys.exit(3)
|
||||
|
||||
if g.debug:
|
||||
print "TX (cl): ", repr(tx_arg)
|
||||
print "TX (proc): ", repr(tx)
|
||||
|
||||
import decimal
|
||||
try:
|
||||
for i in tx.keys():
|
||||
tx[i] = trim_exponent(Decimal(tx[i]))
|
||||
except decimal.InvalidOperation:
|
||||
msg("Decimal conversion error in suboption '%s:%s'" % (i,tx[i]))
|
||||
sys.exit(3)
|
||||
|
||||
return tx
|
||||
qmsg("Address mappings OK\n")
|
||||
|
||||
|
||||
def check_addr_comment(label):
|
||||
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 list(label):
|
||||
for ch in label:
|
||||
if ch not in g.addr_label_symbols:
|
||||
msg("'%s': illegal character in label '%s'" % (ch,label))
|
||||
msg("Permitted characters: A-Za-z0-9, plus '%s'" %
|
||||
"', '".join(g.addr_label_punc))
|
||||
msg("""
|
||||
"%s": illegal character in label "%s".
|
||||
Only ASCII printable characters are permitted.
|
||||
""".strip() % (ch,label))
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def parse_addrs_file(f):
|
||||
|
||||
lines = get_lines_from_file(f,"address data",remove_comments=True)
|
||||
|
||||
try:
|
||||
|
|
@ -518,30 +619,23 @@ def parse_addrs_file(f):
|
|||
msg("'%s': invalid first line" % lines[0])
|
||||
elif cbrace != '}':
|
||||
msg("'%s': invalid last line" % cbrace)
|
||||
elif len(seed_id) != 8:
|
||||
elif not is_mmgen_seed(seed_id):
|
||||
msg("'%s': invalid Seed ID" % seed_id)
|
||||
else:
|
||||
try:
|
||||
unhexlify(seed_id)
|
||||
except:
|
||||
msg("'%s': invalid Seed ID" % seed_id)
|
||||
sys.exit(3)
|
||||
|
||||
ret = []
|
||||
for i in lines[1:-1]:
|
||||
d = i.split(None,2)
|
||||
|
||||
try: d[0] = int(d[0])
|
||||
except:
|
||||
if not is_mmgen_num(d[0]):
|
||||
msg("'%s': invalid address num. in line: %s" % (d[0],d))
|
||||
sys.exit(3)
|
||||
|
||||
from mmgen.bitcoin import verify_addr
|
||||
if not verify_addr(d[1]):
|
||||
msg("'%s': invalid address" % d[1])
|
||||
if not is_btc_addr(d[1]):
|
||||
msg("'%s': invalid Bitcoin address" % d[1])
|
||||
sys.exit(3)
|
||||
|
||||
if len(d) == 3: check_addr_comment(d[2])
|
||||
if len(d) == 3:
|
||||
check_addr_label(d[2])
|
||||
|
||||
ret.append(tuple(d))
|
||||
|
||||
|
|
@ -553,52 +647,59 @@ def parse_addrs_file(f):
|
|||
def sign_transaction(c,tx_hex,sig_data,keys=None):
|
||||
|
||||
if keys:
|
||||
msg("%s keys total" % len(keys))
|
||||
qmsg("%s keys total" % len(keys))
|
||||
if g.debug: print "Keys:\n %s" % "\n ".join(keys)
|
||||
|
||||
msg_r("Signing transaction...")
|
||||
from mmgen.rpc import exceptions
|
||||
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
|
||||
except exceptions.InvalidAddressOrKey:
|
||||
msg("Invalid address or key")
|
||||
msg("failed\nInvalid address or key")
|
||||
sys.exit(3)
|
||||
# except:
|
||||
# msg("Failed to sign transaction")
|
||||
# sys.exit(3)
|
||||
|
||||
return sig_tx
|
||||
|
||||
|
||||
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts):
|
||||
def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False):
|
||||
|
||||
seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
|
||||
seed_ids_save = seed_ids[0:]
|
||||
keys = []
|
||||
seed_ids = list(set([i[:8] for i in mmgen_addrs]))
|
||||
seed_ids_save = seed_ids[0:] # deep copy
|
||||
ret = []
|
||||
|
||||
seeds_keys = [i for i in seed_ids if i in seeds]
|
||||
|
||||
while seed_ids:
|
||||
infile = False
|
||||
if infiles:
|
||||
infile = infiles.pop()
|
||||
seed = get_seed(infile,opts)
|
||||
elif "from_brain" in opts or "from_mnemonic" in opts or "from_seed" in opts:
|
||||
msg("Need data for seed ID %s" % seed_ids[0])
|
||||
seed = get_seed_retry("",opts)
|
||||
if seeds_keys:
|
||||
seed = seeds[seeds_keys.pop()]
|
||||
else:
|
||||
b,p,v = ("A seed","","is") if len(seed_ids) == 1 else ("Seed","s","are")
|
||||
msg("ERROR: %s source%s %s required for the following seed ID%s: %s" %
|
||||
(b,p,v,p," ".join(seed_ids)))
|
||||
sys.exit(2)
|
||||
infile = False
|
||||
if infiles:
|
||||
infile = infiles.pop()
|
||||
seed = get_seed_retry(infile,opts)
|
||||
elif "from_brain" in opts or "from_mnemonic" in opts \
|
||||
or "from_seed" in opts:
|
||||
msg("Need data for seed ID %s" % seed_ids[0])
|
||||
seed = get_seed_retry("",opts)
|
||||
else:
|
||||
b,p,v = ("A seed","","is") if len(seed_ids) == 1 \
|
||||
else ("Seed","s","are")
|
||||
msg("ERROR: %s source%s %s required for the following seed ID%s: %s"%
|
||||
(b,p,v,p," ".join(seed_ids)))
|
||||
sys.exit(2)
|
||||
|
||||
seed_id = make_chksum_8(seed)
|
||||
if seed_id in seed_ids:
|
||||
seed_ids.remove(seed_id)
|
||||
seed_id_addrs = [
|
||||
int(i['account'].split()[0][9:]) for i in mmgen_addrs
|
||||
if i['account'][:8] == seed_id]
|
||||
|
||||
from mmgen.addr import generate_keys
|
||||
keys += [i['wif'] for i in generate_keys(seed, seed_id_addrs)]
|
||||
addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
|
||||
seeds[seed_id] = seed
|
||||
from mmgen.addr import generate_keys,generate_addrs
|
||||
if gen_pairs:
|
||||
o = {"gen_what":"addresses"}
|
||||
ret += [("%s:%s" % (seed_id,i['num']),i['addr'])
|
||||
for i in generate_addrs(seed, addr_ids, o)]
|
||||
else:
|
||||
ret += [i['wif'] for i in generate_keys(seed, addr_ids)]
|
||||
else:
|
||||
if seed_id in seed_ids_save:
|
||||
msg_r("Ignoring duplicate seed source")
|
||||
|
|
@ -610,7 +711,7 @@ def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,opts):
|
|||
msg("Invalid input file: %s" % infile)
|
||||
sys.exit(2)
|
||||
|
||||
return keys
|
||||
return ret
|
||||
|
||||
|
||||
def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
|
||||
|
|
@ -642,15 +743,64 @@ def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
|
|||
return sig_tx
|
||||
|
||||
|
||||
def preverify_keys(addrs_orig, keys_orig):
|
||||
|
||||
addrs,keys,wrong_keys = set(addrs_orig[0:]),set(keys_orig[0:]),[]
|
||||
|
||||
if len(keys) < len(addrs):
|
||||
msg("ERROR: not enough keys (%s) for number of non-%s addresses (%s)" %
|
||||
(len(keys),g.proj_name_cap,len(addrs)))
|
||||
sys.exit(2)
|
||||
|
||||
import mmgen.bitcoin as b
|
||||
|
||||
qmsg_r('Checking that user-supplied key list contains valid keys...')
|
||||
|
||||
invalid_keys = []
|
||||
|
||||
for n,k in enumerate(keys,1):
|
||||
c = False if k[0] == '5' else True
|
||||
if b.wiftohex(k,compressed=c) == False:
|
||||
invalid_keys.append(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)
|
||||
else: qmsg("OK")
|
||||
|
||||
msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
|
||||
|
||||
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
|
||||
else:
|
||||
wrong_keys.append(k)
|
||||
except KeyboardInterrupt:
|
||||
msg("\nSkipping")
|
||||
else:
|
||||
msg("")
|
||||
if wrong_keys:
|
||||
s = "" if len(wrong_keys) == 1 else "s"
|
||||
msg("%s extra key%s found" % (len(wrong_keys),s))
|
||||
|
||||
if addrs:
|
||||
s = "" if len(addrs) == 1 else "es"
|
||||
msg("No keys found for the following non-%s address%s:" %
|
||||
(g.proj_name_cap,s))
|
||||
print " %s" % "\n ".join(addrs)
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def missing_keys_errormsg(other_addrs):
|
||||
msg("""
|
||||
A key file (option '-f') or wallet.dat (option '-w') must be supplied
|
||||
for the following non-mmgen address%s: %s""" %
|
||||
("" if len(other_addrs) == 1 else "es",
|
||||
" ".join([i['address'] for i in other_addrs])
|
||||
))
|
||||
|
||||
def get_addr_data(cmd_args):
|
||||
for f in cmd_args:
|
||||
data = parse_addrs_file(f)
|
||||
print repr(data); sys.exit() # DEBUG
|
||||
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])
|
||||
|
|
|
|||
226
mmgen/util.py
226
mmgen/util.py
|
|
@ -23,124 +23,21 @@ import sys
|
|||
import mmgen.config as g
|
||||
from binascii import hexlify,unhexlify
|
||||
from mmgen.bitcoin import b58decode_pad
|
||||
from mmgen.term import *
|
||||
|
||||
def msg(s): sys.stderr.write(s + "\n")
|
||||
def msg_r(s): sys.stderr.write(s)
|
||||
def qmsg(s):
|
||||
if not g.quiet: sys.stderr.write(s + "\n")
|
||||
def qmsg_r(s):
|
||||
if not g.quiet: 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)
|
||||
|
||||
def msg(s): sys.stderr.write(s + "\n")
|
||||
def msg_r(s): sys.stderr.write(s)
|
||||
def bail(): sys.exit(9)
|
||||
|
||||
def kb_hold_protect_unix():
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
tty.setcbreak(fd)
|
||||
|
||||
timeout = float(0.3)
|
||||
|
||||
try:
|
||||
while True:
|
||||
key = select([sys.stdin], [], [], timeout)[0]
|
||||
if key: sys.stdin.read(1)
|
||||
else: break
|
||||
except:
|
||||
print "\nUser interrupt"
|
||||
sys.exit(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
|
||||
def get_keypress_unix(prompt="",immed_chars=""):
|
||||
|
||||
msg_r(prompt)
|
||||
timeout = float(0.3)
|
||||
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
tty.setcbreak(fd)
|
||||
|
||||
try:
|
||||
while True:
|
||||
select([sys.stdin], [], [], False)
|
||||
ch = sys.stdin.read(1)
|
||||
if immed_chars == "ALL" or ch in immed_chars:
|
||||
return ch
|
||||
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
|
||||
return ch
|
||||
second_key = select([sys.stdin], [], [], timeout)[0]
|
||||
if second_key: continue
|
||||
else: return ch
|
||||
except:
|
||||
print "\nUser interrupt"
|
||||
sys.exit(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
|
||||
def kb_hold_protect_mswin():
|
||||
|
||||
timeout = float(0.5)
|
||||
|
||||
try:
|
||||
while True:
|
||||
hit_time = time.time()
|
||||
while True:
|
||||
if msvcrt.kbhit():
|
||||
msvcrt.getch()
|
||||
break
|
||||
if float(time.time() - hit_time) > timeout:
|
||||
return
|
||||
except:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def get_keypress_mswin(prompt="",immed_chars=""):
|
||||
|
||||
msg_r(prompt)
|
||||
timeout = float(0.5)
|
||||
|
||||
try:
|
||||
while True:
|
||||
if msvcrt.kbhit():
|
||||
ch = msvcrt.getch()
|
||||
|
||||
if ord(ch) == 3: raise KeyboardInterrupt
|
||||
|
||||
if immed_chars == "ALL" or ch in immed_chars:
|
||||
return ch
|
||||
if immed_chars == "ALL_EXCEPT_ENTER" and not ch in "\n\r":
|
||||
return ch
|
||||
|
||||
hit_time = time.time()
|
||||
|
||||
while True:
|
||||
if msvcrt.kbhit(): break
|
||||
if float(time.time() - hit_time) > timeout:
|
||||
return ch
|
||||
except:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
try:
|
||||
import tty, termios
|
||||
from select import select
|
||||
get_char = get_keypress_unix
|
||||
kb_hold_protect = kb_hold_protect_unix
|
||||
except:
|
||||
try:
|
||||
import msvcrt, time
|
||||
get_char = get_keypress_mswin
|
||||
kb_hold_protect = kb_hold_protect_mswin
|
||||
except:
|
||||
if not sys.platform.startswith("linux") \
|
||||
and not sys.platform.startswith("win"):
|
||||
msg("Unsupported platform: %s" % sys.platform)
|
||||
msg("This program currently runs only on Linux and Windows")
|
||||
else:
|
||||
msg("Unable to set terminal mode")
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
def my_raw_input(prompt,echo=True,allowed_chars=""):
|
||||
try:
|
||||
if echo:
|
||||
|
|
@ -148,8 +45,8 @@ def my_raw_input(prompt,echo=True,allowed_chars=""):
|
|||
else:
|
||||
from getpass import getpass
|
||||
reply = getpass(prompt)
|
||||
except:
|
||||
print "\nUser interrupt"
|
||||
except KeyboardInterrupt:
|
||||
msg("\nUser interrupt")
|
||||
sys.exit(1)
|
||||
|
||||
kb_hold_protect()
|
||||
|
|
@ -202,7 +99,7 @@ cmessages = {
|
|||
'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),
|
||||
""".format(g.proj_name_cap),
|
||||
'brain_warning': """
|
||||
############################## EXPERTS ONLY! ##############################
|
||||
|
||||
|
|
@ -223,7 +120,7 @@ future, you must continue using these same parameters
|
|||
|
||||
def confirm_or_exit(message, question, expect="YES"):
|
||||
|
||||
msg("")
|
||||
vmsg("")
|
||||
|
||||
m = message.strip()
|
||||
if m: msg(m)
|
||||
|
|
@ -353,7 +250,7 @@ def get_new_passphrase(what, opts):
|
|||
pw2 = " ".join(_get_words_from_user(("Repeat %s: " % what),opts))
|
||||
if g.debug: print "Passphrases: [%s] [%s]" % (pw,pw2)
|
||||
if pw == pw2:
|
||||
msg("%ss match" % what.capitalize())
|
||||
vmsg("%ss match" % what.capitalize())
|
||||
break
|
||||
else:
|
||||
msg("%ss do not match" % what.capitalize())
|
||||
|
|
@ -362,7 +259,7 @@ def get_new_passphrase(what, opts):
|
|||
g.passwd_max_tries)
|
||||
sys.exit(2)
|
||||
|
||||
if pw == "": msg("WARNING: Empty passphrase")
|
||||
if pw == "": qmsg("WARNING: Empty passphrase")
|
||||
return pw
|
||||
|
||||
|
||||
|
|
@ -384,17 +281,17 @@ def _get_seed_from_brain_passphrase(words,opts):
|
|||
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)
|
||||
msg_r("Hashing brainwallet data. Please wait...")
|
||||
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)
|
||||
msg("Done")
|
||||
vmsg("Done")
|
||||
return seed
|
||||
|
||||
|
||||
def encrypt_seed(seed, key, opts):
|
||||
def encrypt_seed(seed, key):
|
||||
"""
|
||||
Encrypt a seed for a {} deterministic wallet
|
||||
""".format(g.proj_name)
|
||||
Encrypt a seed for an {} deterministic wallet
|
||||
""".format(g.proj_name_cap)
|
||||
|
||||
# 192-bit seed is 24 bytes -> not multiple of 16. Must use MODE_CTR
|
||||
from Crypto.Cipher import AES
|
||||
|
|
@ -403,14 +300,14 @@ def encrypt_seed(seed, key, opts):
|
|||
c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
|
||||
enc_seed = c.encrypt(seed)
|
||||
|
||||
msg_r("Performing a test decryption of the seed...")
|
||||
vmsg_r("Performing a test decryption of the seed...")
|
||||
|
||||
c = AES.new(key, AES.MODE_CTR,counter=Counter.new(128))
|
||||
dec_seed = c.decrypt(enc_seed)
|
||||
|
||||
if dec_seed == seed: msg("done")
|
||||
if dec_seed == seed: vmsg("done")
|
||||
else:
|
||||
msg("FAILED.\nDecrypted seed doesn't match original seed")
|
||||
msg("ERROR.\nDecrypted seed doesn't match original seed")
|
||||
sys.exit(2)
|
||||
|
||||
return enc_seed
|
||||
|
|
@ -576,11 +473,11 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
|||
|
||||
chk = make_chksum_6(" ".join(lines))
|
||||
|
||||
confirm = False if 'quiet' in opts else True
|
||||
confirm = False if g.quiet else True
|
||||
write_to_file(outfile, "\n".join((chk,)+lines)+"\n", confirm)
|
||||
|
||||
msg("Wallet saved to file '%s'" % outfile)
|
||||
if 'verbose' in opts:
|
||||
if g.verbose:
|
||||
_display_control_data(label,metadata,hash_preset,salt,enc_seed)
|
||||
|
||||
|
||||
|
|
@ -592,10 +489,10 @@ def write_walletdat_dump_to_file(wallet_id,data,num_keys,ext,what,opts):
|
|||
msg("wallet.dat %s saved to file '%s'" % (what,outfile))
|
||||
|
||||
|
||||
def compare_checksums(chksum1, desc1, chksum2, desc2):
|
||||
def _compare_checksums(chksum1, desc1, chksum2, desc2):
|
||||
|
||||
if chksum1.lower() == chksum2.lower():
|
||||
msg("OK (%s)" % chksum1.upper())
|
||||
vmsg("OK (%s)" % chksum1.upper())
|
||||
return True
|
||||
else:
|
||||
if g.debug:
|
||||
|
|
@ -608,6 +505,8 @@ def _is_hex(s):
|
|||
except: return False
|
||||
else: return True
|
||||
|
||||
def match_ext(addr,ext):
|
||||
return addr.split(".")[-1] == ext
|
||||
|
||||
def _check_mmseed_format(words):
|
||||
|
||||
|
|
@ -629,10 +528,7 @@ def _check_mmseed_format(words):
|
|||
return valid
|
||||
|
||||
|
||||
def check_wallet_format(infile, lines, opts):
|
||||
|
||||
def vmsg(s):
|
||||
if 'verbose' in opts: msg(s)
|
||||
def _check_wallet_format(infile, lines):
|
||||
|
||||
what = "wallet file '%s'" % infile
|
||||
valid = False
|
||||
|
|
@ -660,17 +556,19 @@ def _check_chksum_6(chk,val,desc,infile):
|
|||
msg("%s checksum passed: %s" % (desc.capitalize(),chk))
|
||||
|
||||
|
||||
def get_data_from_wallet(infile,opts,silent=False):
|
||||
def get_data_from_wallet(infile,silent=False):
|
||||
|
||||
# Don't make this a qmsg: User will be prompted for passphrase and must see
|
||||
# the filename.
|
||||
if not silent:
|
||||
msg("Getting {} wallet data from file '{}'".format(g.proj_name,infile))
|
||||
msg("Getting {} wallet data from file '{}'".format(g.proj_name_cap,infile))
|
||||
|
||||
f = open_file_or_exit(infile, 'r')
|
||||
|
||||
lines = [i.strip() for i in f.readlines()]
|
||||
f.close()
|
||||
|
||||
check_wallet_format(infile, lines, opts)
|
||||
_check_wallet_format(infile, lines)
|
||||
|
||||
label = lines[1]
|
||||
|
||||
|
|
@ -711,7 +609,7 @@ def _get_words_from_user(prompt, opts):
|
|||
|
||||
|
||||
def _get_words_from_file(infile,what):
|
||||
msg("Getting %s from file '%s'" % (what,infile))
|
||||
qmsg("Getting %s from file '%s'" % (what,infile))
|
||||
f = open_file_or_exit(infile, 'r')
|
||||
# split() also strips
|
||||
words = f.read().split()
|
||||
|
|
@ -721,7 +619,8 @@ def _get_words_from_file(infile,what):
|
|||
|
||||
|
||||
def get_lines_from_file(infile,what="",remove_comments=False):
|
||||
if what != "": msg("Getting %s from file '%s'" % (what,infile))
|
||||
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:
|
||||
|
|
@ -738,7 +637,7 @@ def get_lines_from_file(infile,what="",remove_comments=False):
|
|||
|
||||
|
||||
def get_data_from_file(infile,what="data"):
|
||||
msg("Getting %s from file '%s'" % (what,infile))
|
||||
qmsg("Getting %s from file '%s'" % (what,infile))
|
||||
f = open_file_or_exit(infile,'r')
|
||||
data = f.read()
|
||||
f.close()
|
||||
|
|
@ -755,18 +654,18 @@ def _get_seed_from_seed_data(words):
|
|||
seed_b58 = "".join(words[1:])
|
||||
|
||||
chk = make_chksum_6(seed_b58)
|
||||
msg_r("Validating %s checksum..." % g.seed_ext)
|
||||
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)
|
||||
return False
|
||||
|
||||
msg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed)))
|
||||
vmsg("%s data produces seed ID: %s" % (g.seed_ext,make_chksum_8(seed)))
|
||||
return seed
|
||||
else:
|
||||
msg("Invalid checksum for {} seed".format(g.proj_name))
|
||||
msg("Invalid checksum for {} seed".format(g.proj_name_cap))
|
||||
return False
|
||||
|
||||
|
||||
|
|
@ -791,7 +690,8 @@ def get_mmgen_passphrase(prompt,opts):
|
|||
def get_bitcoind_passphrase(prompt,opts):
|
||||
if 'passwd_file' in opts:
|
||||
mark_passwd_file_as_used(opts)
|
||||
return get_data_from_file(opts['passwd_file'],"passphrase").strip("\r\n")
|
||||
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)
|
||||
|
|
@ -800,14 +700,14 @@ def get_bitcoind_passphrase(prompt,opts):
|
|||
def get_seed_from_wallet(
|
||||
infile,
|
||||
opts,
|
||||
prompt="Enter {} wallet passphrase: ".format(g.proj_name),
|
||||
prompt="Enter {} wallet passphrase: ".format(g.proj_name_cap),
|
||||
silent=False
|
||||
):
|
||||
|
||||
wdata = get_data_from_wallet(infile,opts,silent=silent)
|
||||
wdata = get_data_from_wallet(infile,silent=silent)
|
||||
label,metadata,hash_preset,salt,enc_seed = wdata
|
||||
|
||||
if 'verbose' in opts: _display_control_data(*wdata)
|
||||
if g.verbose: _display_control_data(*wdata)
|
||||
|
||||
passwd = get_mmgen_passphrase(prompt,opts)
|
||||
|
||||
|
|
@ -818,21 +718,21 @@ def get_seed_from_wallet(
|
|||
|
||||
def make_key(passwd, salt, hash_preset):
|
||||
|
||||
msg_r("Hashing passphrase. Please wait...")
|
||||
vmsg_r("Hashing passphrase. Please wait...")
|
||||
key = _scrypt_hash_passphrase(passwd, salt, hash_preset)
|
||||
msg("done")
|
||||
vmsg("done")
|
||||
return key
|
||||
|
||||
|
||||
def decrypt_seed(enc_seed, key, seed_id, key_id):
|
||||
|
||||
msg_r("Checking key...")
|
||||
vmsg_r("Checking key...")
|
||||
chk = make_chksum_8(key)
|
||||
if not compare_checksums(chk, "of key", key_id, "in header"):
|
||||
if not _compare_checksums(chk, "of key", key_id, "in header"):
|
||||
msg("Incorrect passphrase")
|
||||
return False
|
||||
|
||||
msg_r("Decrypting seed with key...")
|
||||
vmsg_r("Decrypting seed with key...")
|
||||
|
||||
from Crypto.Cipher import AES
|
||||
from Crypto.Util import Counter
|
||||
|
|
@ -841,13 +741,13 @@ def decrypt_seed(enc_seed, key, seed_id, key_id):
|
|||
dec_seed = c.decrypt(enc_seed)
|
||||
|
||||
chk = make_chksum_8(dec_seed)
|
||||
if compare_checksums(chk,"of decrypted seed",seed_id,"in header"):
|
||||
msg("Passphrase is OK")
|
||||
if _compare_checksums(chk,"of decrypted seed",seed_id,"in header"):
|
||||
qmsg("Passphrase is OK")
|
||||
else:
|
||||
if not g.debug:
|
||||
msg_r("Checking key ID...")
|
||||
chk = make_chksum_8(key)
|
||||
if compare_checksums(chk, "of key", key_id, "in header"):
|
||||
if _compare_checksums(chk, "of key", key_id, "in header"):
|
||||
msg("Key ID is correct but decryption of seed failed")
|
||||
else:
|
||||
msg("Incorrect passphrase")
|
||||
|
|
@ -894,10 +794,10 @@ 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)
|
||||
if 'quiet' not in opts:
|
||||
if not g.quiet:
|
||||
confirm_or_exit(
|
||||
cmessages['brain_warning'].format(
|
||||
g.proj_name.capitalize(), *_get_from_brain_opt_params(opts)),
|
||||
g.proj_name_cap, *_get_from_brain_opt_params(opts)),
|
||||
"continue")
|
||||
prompt = "Enter brainwallet passphrase: "
|
||||
words = _get_words(infile,"brainwallet data",prompt,opts)
|
||||
|
|
@ -954,10 +854,10 @@ def do_pager(text):
|
|||
else:
|
||||
try:
|
||||
p.communicate(text+end+"\n")
|
||||
except:
|
||||
except KeyboardInterrupt:
|
||||
# Has no effect. Why?
|
||||
if pager != "less":
|
||||
msg("\n(Interrupted by user)\n")
|
||||
msg("\n(User interrupt)\n")
|
||||
finally:
|
||||
msg_r("\r")
|
||||
break
|
||||
|
|
|
|||
|
|
@ -20,14 +20,15 @@ walletgen.py: Routines used for seed generation and wallet creation
|
|||
"""
|
||||
|
||||
import sys
|
||||
from mmgen.util import msg, msg_r, get_char, prompt_and_get_char
|
||||
import mmgen.config as g
|
||||
from mmgen.util import msg, msg_r, qmsg, qmsg_r, get_char, prompt_and_get_char
|
||||
from binascii import hexlify
|
||||
|
||||
def get_random_data_from_user(opts):
|
||||
|
||||
ulen = opts['usr_randlen']
|
||||
|
||||
if 'quiet' in opts:
|
||||
if g.quiet:
|
||||
msg("Enter %s random symbols" % ulen)
|
||||
else:
|
||||
msg("""
|
||||
|
|
@ -54,7 +55,7 @@ displayed on the screen.
|
|||
intervals.append(now - saved_time)
|
||||
saved_time = now
|
||||
|
||||
if 'quiet' in opts:
|
||||
if g.quiet:
|
||||
msg_r("\r")
|
||||
else:
|
||||
msg_r("\rThank you. That's enough." + " "*15 + "\n\n")
|
||||
|
|
|
|||
3
setup.py
3
setup.py
|
|
@ -3,7 +3,7 @@ from distutils.core import setup
|
|||
|
||||
setup(
|
||||
name = 'mmgen',
|
||||
version = '0.6.9',
|
||||
version = '0.7.0',
|
||||
author = 'Philemon',
|
||||
author_email = 'mmgen-py@yandex.com',
|
||||
url = 'https://github.com/mmgen/mmgen',
|
||||
|
|
@ -19,6 +19,7 @@ setup(
|
|||
'mmgen.mnemonic',
|
||||
'mmgen.mn_tirosh',
|
||||
'mmgen.Opts',
|
||||
'mmgen.term',
|
||||
'mmgen.tx',
|
||||
'mmgen.util',
|
||||
'mmgen.walletgen',
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue