Version 0.7.0

This commit is contained in:
The MMGen Project 2014-04-09 22:45:23 +04:00
commit 08c5b76805
28 changed files with 934 additions and 627 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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")

View file

@ -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)

View file

@ -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)

View file

@ -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()

View file

@ -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)

View file

@ -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:

View file

@ -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),

View file

@ -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

View file

@ -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

View file

@ -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()

View file

@ -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
View 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())

View file

@ -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]'],
}

View file

@ -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):

View file

@ -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])

View file

@ -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

View file

@ -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")

View file

@ -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',