TX script improvements, added features (tx's with both Satoshi and MMGen inputs)
modified: mmgen-addrgen modified: mmgen-addrimport modified: mmgen-txcreate modified: mmgen-txsend modified: mmgen-txsign modified: mmgen-walletchk modified: mmgen-walletgen modified: mmgen/addr.py modified: mmgen/config.py modified: mmgen/tx.py modified: mmgen/utils.py
This commit is contained in:
parent
db5ce0295e
commit
7cfca299a3
11 changed files with 257 additions and 97 deletions
|
|
@ -72,9 +72,12 @@ Address range may be a single number or a range in the form XXX-YYY{}
|
|||
If available, external 'keyconv' program will be used for address generation
|
||||
|
||||
Data for the --from-<what> options will be taken from <infile> if <infile>
|
||||
is specified. Otherwise, the user will be prompted to enter the data. Note
|
||||
that passphrase data in a file may be arranged in free-form fashion, using
|
||||
any combination of spaces, tabs or newlines to separate words
|
||||
is specified. Otherwise, the user will be prompted to enter the data.
|
||||
|
||||
For passphrases all combinations of whitespace are equal, and leading and
|
||||
trailing space are ignored. This permits reading passphrase data from a
|
||||
multi-line file with free spacing and indentation. This is particularly
|
||||
convenient for long brainwallet passphrases, for example.
|
||||
|
||||
BRAINWALLET NOTE:
|
||||
|
||||
|
|
@ -158,7 +161,7 @@ else:
|
|||
|
||||
seed = get_seed(infile,opts)
|
||||
seed_id = make_chksum_8(seed)
|
||||
addr_data = generate_addrs(seed, start, end, opts)
|
||||
addr_data = generate_addrs(seed, range(start, end+1), opts)
|
||||
addr_data_str = format_addr_data(addr_data, seed_id, opts)
|
||||
|
||||
# Output data:
|
||||
|
|
|
|||
|
|
@ -50,7 +50,12 @@ check_infile(cmd_args[0])
|
|||
|
||||
seed_id,addr_data = parse_addrs_file(cmd_args[0])
|
||||
|
||||
from mmgen.tx import connect_to_bitcoind
|
||||
from mmgen.tx import check_wallet_addr_label,connect_to_bitcoind
|
||||
|
||||
for i in addr_data:
|
||||
i[2] = " ".join(i[2:]) if len(i) > 2 else ""
|
||||
check_wallet_addr_label(i[2])
|
||||
|
||||
c = connect_to_bitcoind(http_timeout=3600)
|
||||
|
||||
message = """
|
||||
|
|
@ -60,8 +65,7 @@ low-powered computer such as a netbook.
|
|||
confirm_or_exit(message, "continue", expect="YES")
|
||||
|
||||
for n,i in enumerate(addr_data):
|
||||
comment = " " + " ".join(i[2:]) if len(i) > 2 else ""
|
||||
label = "%s:%s%s" % (seed_id,str(i[0]),comment)
|
||||
label = "%s:%s %s" % (seed_id,str(i[0]),i[2])
|
||||
msg("Importing %-6s %-34s (%s)" % (
|
||||
("%s/%s:" % (n+1,len(addr_data))),
|
||||
i[1], label)
|
||||
|
|
|
|||
|
|
@ -94,7 +94,30 @@ msg("Total amount to spend: %s BTC" % send_amt)
|
|||
msg("%s unspent outputs total" % len(unspent))
|
||||
|
||||
while True:
|
||||
sel_unspent = select_outputs(unspent,"Choose the outputs to spend: ")
|
||||
sel_nums = select_outputs(unspent,"Choose the outputs to spend: ")
|
||||
sel_unspent = [unspent[i] for i in sel_nums]
|
||||
mmgen_sel,other_sel = [],[]
|
||||
for i in sel_nums:
|
||||
if verify_mmgen_label(unspent[i].account):
|
||||
mmgen_sel.append(i)
|
||||
else:
|
||||
other_sel.append(i)
|
||||
|
||||
if mmgen_sel and other_sel:
|
||||
keygen_args = [unspent[i].account.split()[0][9:] for i in mmgen_sel]
|
||||
msg("""
|
||||
NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs,
|
||||
which makes the signing process more complicated. When signing the
|
||||
transaction, keys for the non-mmgen inputs must be supplied in a separate
|
||||
file using mmgen-txsign's '-k' option. Alternatively, you may import the
|
||||
mmgen keys into the wallet.dat of your offline bitcoind, first running
|
||||
mmgen-keygen with address list '%s' to generate the keys. Finally, run
|
||||
mmgen-txsign with the '-f' option to force the use of wallet.dat as the
|
||||
key source.
|
||||
""".strip() % ",".join(sorted(keygen_args)))
|
||||
if not user_confirm("Accept?"):
|
||||
continue
|
||||
|
||||
total_in = trim_exponent(sum([o.amount for o in sel_unspent]))
|
||||
change = trim_exponent(total_in - (send_amt + tx_fee))
|
||||
|
||||
|
|
@ -102,8 +125,9 @@ while True:
|
|||
prompt = "Transaction produces %s BTC in change. OK?" % change
|
||||
if user_confirm(prompt,default_yes=True):
|
||||
break
|
||||
else:
|
||||
msg(txmsg['not_enough_btc'] % change)
|
||||
|
||||
msg(txmsg['not_enough_btc'] % change)
|
||||
|
||||
if change > 0 and not change_addr:
|
||||
msg(txmsg['throwaway_change'] % (change, total_in-tx_fee))
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ except:
|
|||
|
||||
if not 'quiet' in opts: do_license_msg()
|
||||
|
||||
msg("Signed transaction file '%s' appears valid" % infile)
|
||||
msg("\nSigned transaction file '%s' appears valid" % infile)
|
||||
|
||||
warn = "Once this transaction is sent, there's no taking it back!"
|
||||
what = "broadcast this transaction to the network"
|
||||
|
|
|
|||
155
mmgen-txsign
155
mmgen-txsign
|
|
@ -26,47 +26,98 @@ from mmgen.Opts import *
|
|||
from mmgen.license import *
|
||||
from mmgen.config import *
|
||||
from mmgen.tx import *
|
||||
from mmgen.utils import check_opts, msg, user_confirm, check_infile, get_lines_from_file, my_getpass, my_raw_input
|
||||
|
||||
prog_name = sys.argv[0].split("/")[-1]
|
||||
from mmgen.utils import *
|
||||
|
||||
help_data = {
|
||||
'prog_name': prog_name,
|
||||
'prog_name': sys.argv[0].split("/")[-1],
|
||||
'desc': "Sign a Bitcoin transaction generated by mmgen-txcreate",
|
||||
'usage': "[opts] <transaction file>",
|
||||
'usage': "[opts] <transaction file> [mmgen wallet/seed/mnemonic file]",
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-d, --outdir d Specify an alternate directory 'd' for output
|
||||
-e, --echo-passphrase Print passphrase to screen when typing it
|
||||
-f, --force-wallet-dat Force the use of wallet.dat as a key source
|
||||
-i, --info Display information about the transaction and exit
|
||||
-k, --keys-from-file k Provide additional key data from file 'k'
|
||||
-q, --quiet Suppress warnings; overwrite files without asking
|
||||
"""
|
||||
|
||||
-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)
|
||||
-m, --from-mnemonic Generate keys from an electrum-like mnemonic
|
||||
-s, --from-seed Generate keys from a seed in .{} format
|
||||
|
||||
Data for the --from-<what> options will be taken from a file if a second
|
||||
file is specified on the command line. Otherwise, the user will be
|
||||
prompted to enter the data.
|
||||
""".format(seed_ext)
|
||||
}
|
||||
|
||||
short_opts = "hd:eiq"
|
||||
long_opts = "help","outdir=","echo_passphrase","info","quiet"
|
||||
short_opts = "hd:efik:qb:ms"
|
||||
long_opts = "help","outdir=","echo_passphrase","force_wallet_dat","info",\
|
||||
"keys_from_file=","quiet","from_brain=","from_mnemonic","from_seed"
|
||||
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
# Exits on invalid input
|
||||
check_opts(opts, ('outdir',))
|
||||
check_opts(opts, ('outdir','from_brain'))
|
||||
if 'keys_from_file' in opts: check_infile(opts['keys_from_file'])
|
||||
|
||||
if debug:
|
||||
print "Processed options: %s" % repr(opts)
|
||||
print "Cmd args: %s" % repr(cmd_args)
|
||||
|
||||
if len(cmd_args) == 1:
|
||||
infile = cmd_args[0]
|
||||
check_infile(infile)
|
||||
if len(cmd_args) in (1,2):
|
||||
tx_file = cmd_args[0]
|
||||
check_infile(tx_file)
|
||||
else: usage(help_data)
|
||||
|
||||
# Begin execution
|
||||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
tx_data = get_lines_from_file(infile,"transaction data")
|
||||
tx_data = get_lines_from_file(tx_file,"transaction data")
|
||||
|
||||
metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data)
|
||||
metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
|
||||
|
||||
# Are inputs mmgen addresses?
|
||||
infile,mmgen_addrs,other_addrs = "",[],[]
|
||||
# Check that all the seed IDs are the same:
|
||||
|
||||
for i in inputs_data:
|
||||
if verify_mmgen_label(i['account']):
|
||||
mmgen_addrs.append(i)
|
||||
else:
|
||||
other_addrs.append(i)
|
||||
|
||||
if mmgen_addrs:
|
||||
a_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
|
||||
if len(a_ids) != 1:
|
||||
msg("Addresses come from different seeds! (%s)" % " ".join(a_ids))
|
||||
sys.exit(3)
|
||||
|
||||
if len(cmd_args) == 2:
|
||||
infile = cmd_args[1]
|
||||
else:
|
||||
if "from_brain" in opts \
|
||||
or "from_mnemonic" in opts \
|
||||
or "from_seed" in opts:
|
||||
infile = ""
|
||||
else:
|
||||
msg("Inputs contain mmgen addresses. An MMGen wallet file must be specified on the command line (or use the '-b', '-m' or '-s' options).".strip())
|
||||
sys.exit(2)
|
||||
|
||||
if other_addrs:
|
||||
if 'keys_from_file' in opts:
|
||||
add_keys = get_lines_from_file(opts['keys_from_file'])
|
||||
else:
|
||||
msg("""
|
||||
A key file must be supplied (option '-f') for the following non-mmgen
|
||||
address%s: %s""" % (
|
||||
"" if len(other_addrs) == 1 else "es",
|
||||
" ".join([i['address'] for i in other_addrs])
|
||||
))
|
||||
sys.exit(2)
|
||||
|
||||
if 'info' in opts:
|
||||
view_tx_data(c,inputs_data,tx_hex,metadata)
|
||||
|
|
@ -74,46 +125,64 @@ if 'info' in opts:
|
|||
|
||||
if not 'quiet' in opts and not 'info' in opts: do_license_msg()
|
||||
|
||||
msg("Successfully opened transaction file '%s'" % infile)
|
||||
msg("Successfully opened transaction file '%s'" % tx_file)
|
||||
|
||||
if user_confirm("View transaction data? ",default_yes=False):
|
||||
view_tx_data(c,inputs_data,tx_hex,metadata)
|
||||
|
||||
prompt = "Enter passphrase for bitcoind wallet: "
|
||||
if 'echo_passphrase' in opts:
|
||||
password = my_raw_input(prompt)
|
||||
if mmgen_addrs:
|
||||
seed = get_seed(infile,opts)
|
||||
seed_id = make_chksum_8(seed)
|
||||
|
||||
if seed_id != a_ids[0]:
|
||||
msg("Seed ID of wallet (%s) doesn't match that of addresses (%s)"
|
||||
% (seed_id,a_ids[0]))
|
||||
sys.exit(3)
|
||||
addr_nums = [int(i['account'].split()[0][9:]) for i in mmgen_addrs]
|
||||
from mmgen.addr import generate_addrs
|
||||
o = {'no_addresses': True, 'gen_what': "keys"}
|
||||
keys = [i['wif'] for i in generate_addrs(seed, addr_nums, o)]
|
||||
if other_addrs: keys += add_keys
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
|
||||
except:
|
||||
msg("Failed to sign transaction")
|
||||
sys.exit(3)
|
||||
else:
|
||||
password = my_getpass(prompt)
|
||||
prompt = "Enter passphrase for bitcoind wallet: "
|
||||
if 'echo_passphrase' in opts:
|
||||
password = my_raw_input(prompt)
|
||||
else:
|
||||
password = my_getpass(prompt)
|
||||
|
||||
wallet_enc = True
|
||||
from bitcoinrpc import exceptions
|
||||
wallet_enc = True
|
||||
from mmgen.rpc import exceptions
|
||||
|
||||
try:
|
||||
c.walletpassphrase(password, 9999)
|
||||
except exceptions.WalletWrongEncState:
|
||||
msg("Wallet is unencrypted")
|
||||
wallet_enc = False
|
||||
except exceptions.WalletPassphraseIncorrect:
|
||||
msg("Passphrase incorrect")
|
||||
sys.exit(3)
|
||||
except exceptions.WalletAlreadyUnlocked:
|
||||
msg("WARNING: Wallet already unlocked!")
|
||||
else:
|
||||
msg("Passphrase OK")
|
||||
try:
|
||||
c.walletpassphrase(password, 9999)
|
||||
except exceptions.WalletWrongEncState:
|
||||
msg("Wallet is unencrypted")
|
||||
wallet_enc = False
|
||||
except exceptions.WalletPassphraseIncorrect:
|
||||
msg("Passphrase incorrect")
|
||||
sys.exit(3)
|
||||
except exceptions.WalletAlreadyUnlocked:
|
||||
msg("WARNING: Wallet already unlocked!")
|
||||
else:
|
||||
msg("Passphrase OK")
|
||||
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data)
|
||||
except:
|
||||
msg("Failed to sign transaction")
|
||||
if wallet_enc:
|
||||
c.walletlock()
|
||||
msg("Locking wallet")
|
||||
sys.exit(3)
|
||||
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data)
|
||||
# sig_tx = {'hex':"deadbeef0123456789ab",'complete':True}
|
||||
except:
|
||||
msg("Failed to sign transaction")
|
||||
if wallet_enc:
|
||||
c.walletlock()
|
||||
msg("Locking wallet")
|
||||
sys.exit(3)
|
||||
|
||||
if wallet_enc:
|
||||
c.walletlock()
|
||||
msg("Locking wallet")
|
||||
|
||||
if sig_tx['complete']:
|
||||
msg("Signing completed")
|
||||
|
|
|
|||
|
|
@ -21,11 +21,12 @@ mmgen-walletchk: Check integrity of a mmgen deterministic wallet, display
|
|||
"""
|
||||
|
||||
import sys
|
||||
import mmgen.Opts as Opts
|
||||
from mmgen.Opts import *
|
||||
|
||||
from mmgen.utils import *
|
||||
|
||||
help_data = {
|
||||
'prog_name': sys.argv[0].split("/")[-1],
|
||||
'desc': """Check integrity of a %s deterministic wallet, display
|
||||
its information and export seed and mnemonic data."""\
|
||||
% proj_name,
|
||||
|
|
@ -45,7 +46,7 @@ short_opts = "hd:emsSv"
|
|||
long_opts = "help","outdir=","echo_passphrase","export_mnemonic",\
|
||||
"export_seed","stdout","verbose"
|
||||
|
||||
opts,cmd_args = Opts.process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
opts,cmd_args = process_opts(sys.argv,help_data,short_opts,long_opts)
|
||||
|
||||
check_opts(opts, ('outdir',))
|
||||
|
||||
|
|
|
|||
|
|
@ -59,8 +59,11 @@ By default (i.e. when invoked without any of the '--from-<what>' options),
|
|||
|
||||
Data for the --from-<what> options will be taken from <infile> if <infile>
|
||||
is specified. Otherwise, the user will be prompted to enter the data.
|
||||
Note that passphrase data may be input in free-form fashion, using any
|
||||
combination of spaces or tabs (or newlines, in a file) between words.
|
||||
|
||||
For passphrases all combinations of whitespace are equal, and leading and
|
||||
trailing space are ignored. This permits reading passphrase data from a
|
||||
multi-line file with free spacing and indentation. This is particularly
|
||||
convenient for long brainwallet passphrases, for example.
|
||||
|
||||
BRAINWALLET NOTE:
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ from hashlib import sha256, sha512
|
|||
from binascii import hexlify, unhexlify
|
||||
|
||||
from mmgen.bitcoin import numtowif
|
||||
from mmgen.config import *
|
||||
|
||||
def test_for_keyconv():
|
||||
"""
|
||||
|
|
@ -47,15 +48,15 @@ address generation.
|
|||
return True
|
||||
|
||||
|
||||
def generate_addrs(seed, start, end, opts):
|
||||
def generate_addrs(seed, addrnums, opts):
|
||||
"""
|
||||
generate_addresses(start, end, seed, opts) => None
|
||||
|
||||
Generate a series of Bitcoin addresses from start to end based on a
|
||||
seed, optionally outputting secret keys
|
||||
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
|
||||
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
|
||||
|
|
@ -73,17 +74,16 @@ def generate_addrs(seed, start, end, opts):
|
|||
from subprocess import Popen, PIPE
|
||||
keyconv = "keyconv"
|
||||
|
||||
total_addrs = end - start + 1
|
||||
a,t_addrs,i,out = sorted(addrnums),len(addrnums),0,[]
|
||||
|
||||
addrlist = []
|
||||
while a:
|
||||
seed = sha512(seed).digest(); i += 1 # round /i/
|
||||
if i < a[0]: continue
|
||||
|
||||
for i in range(1, end+1):
|
||||
seed = sha512(seed).digest() # round /i/
|
||||
a.pop(0)
|
||||
|
||||
if i < start: continue
|
||||
|
||||
sys.stderr.write("\rGenerating %s: %s of %s" %
|
||||
(opts['gen_what'], (i-start)+1, total_addrs))
|
||||
sys.stderr.write("\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/
|
||||
sec = sha256(sha256(seed).digest()).hexdigest()
|
||||
|
|
@ -98,18 +98,18 @@ def generate_addrs(seed, start, end, opts):
|
|||
if not 'no_addresses' in opts:
|
||||
if keyconv:
|
||||
p = Popen([keyconv, wif], stdout=PIPE)
|
||||
addr = dict([j.split() for j in p.stdout.readlines()])['Address:']
|
||||
addr = dict([j.split() for j in \
|
||||
p.stdout.readlines()])['Address:']
|
||||
else:
|
||||
addr = privnum2addr(int(sec,16))
|
||||
|
||||
el['addr'] = addr
|
||||
|
||||
addrlist.append(el)
|
||||
out.append(el)
|
||||
|
||||
sys.stderr.write("\rGenerated %s %s-%s%s\n" %
|
||||
(opts['gen_what'], start, end, " "*9))
|
||||
sys.stderr.write("\rGenerated %s %s%s\n"%(t_addrs,opts['gen_what']," "*15))
|
||||
|
||||
return addrlist
|
||||
return out
|
||||
|
||||
|
||||
def format_addr_data(addrlist, seed_chksum, opts):
|
||||
|
|
@ -139,7 +139,16 @@ def format_addr_data(addrlist, seed_chksum, opts):
|
|||
(5 if 'print_secret' in opts else 1) + len(wif_msg)
|
||||
)
|
||||
|
||||
data = []
|
||||
header = """
|
||||
# MMGen address file
|
||||
#
|
||||
# This file is editable.
|
||||
# Everything following a hash symbol '#' is ignored.
|
||||
# A label may be added to the right of each address, and it will be
|
||||
# appended to the bitcoind wallet label upon import (max. 24 characters,
|
||||
# allowed characters: A-Za-z0-9, plus '{}').
|
||||
""".format("', '".join(wallet_addr_label_symbols)).strip()
|
||||
data = [header + "\n"]
|
||||
data.append("%s {" % seed_chksum.upper())
|
||||
|
||||
for el in addrlist:
|
||||
|
|
|
|||
|
|
@ -48,3 +48,4 @@ hash_presets = {
|
|||
'4': [15, 8, 12],
|
||||
'5': [16, 8, 16],
|
||||
}
|
||||
wallet_addr_label_symbols = ".","_",",","-"," "
|
||||
|
|
|
|||
68
mmgen/tx.py
68
mmgen/tx.py
|
|
@ -20,7 +20,7 @@ tx.py: Bitcoin transaction routines
|
|||
"""
|
||||
|
||||
from binascii import unhexlify
|
||||
from mmgen.utils import msg,msg_r,write_to_file,my_raw_input,get_char,make_chksum_8,make_timestamp
|
||||
from mmgen.utils import *
|
||||
import sys, os
|
||||
from decimal import Decimal
|
||||
from mmgen.config import *
|
||||
|
|
@ -110,7 +110,7 @@ def get_cfg_options(cfg_keys):
|
|||
def print_tx_to_file(tx,sel_unspent,send_amt,opts):
|
||||
sig_data = [{"txid":i.txid,"vout":i.vout,"scriptPubKey":i.scriptPubKey}
|
||||
for i in sel_unspent]
|
||||
tx_id = make_chksum_8(unhexlify(tx))
|
||||
tx_id = make_chksum_6(unhexlify(tx)).upper()
|
||||
outfile = "%s[%s].tx" % (tx_id,send_amt)
|
||||
if 'outdir' in opts:
|
||||
outfile = "%s/%s" % (opts['outdir'], outfile)
|
||||
|
|
@ -124,7 +124,7 @@ def print_tx_to_file(tx,sel_unspent,send_amt,opts):
|
|||
|
||||
|
||||
def print_signed_tx_to_file(tx,sig_tx,metadata,opts):
|
||||
tx_id = make_chksum_8(unhexlify(tx))
|
||||
tx_id = make_chksum_6(unhexlify(tx)).upper()
|
||||
outfile = "{}[{}].txsig".format(*metadata[:2])
|
||||
if 'outdir' in opts:
|
||||
outfile = "%s/%s" % (opts['outdir'], outfile)
|
||||
|
|
@ -148,10 +148,11 @@ def sort_and_view(unspent):
|
|||
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)
|
||||
|
||||
fs = " %-4s %-11s %-2s %-34s %13s %-s"
|
||||
fs_hdr = " %-4s %-11s %-4s %-35s %-9s %-s"
|
||||
sort,group,reverse = "",False,False
|
||||
sort,group,mmaddr,reverse = "",False,False,False
|
||||
|
||||
from copy import deepcopy
|
||||
msg("")
|
||||
|
|
@ -178,7 +179,16 @@ def sort_and_view(unspent):
|
|||
for n,i in enumerate(out):
|
||||
amt = str(trim_exponent(i.amount))
|
||||
fill = 8 - len(amt.split(".")[-1]) if "." in amt else 9
|
||||
addr = " |" + "-"*32 if i.skip == "d" else i.address
|
||||
if i.skip == "d":
|
||||
addr = " |" + "-"*32
|
||||
else:
|
||||
if mmaddr:
|
||||
if i.account and verify_mmgen_label(i.account):
|
||||
addr = "%s.. %s" % (i.address[:4],i.account)
|
||||
else:
|
||||
addr = i.address
|
||||
else:
|
||||
addr = i.address
|
||||
txid = " |---" if i.skip == "t" else i.txid[:8]+"..."
|
||||
days = int(i.confirmations * mins_per_block / (60*24))
|
||||
|
||||
|
|
@ -187,24 +197,44 @@ def sort_and_view(unspent):
|
|||
while True:
|
||||
reply = get_char("\n".join(output) +
|
||||
"""\n
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [g]roup
|
||||
Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
|
||||
View options: [g]roup, show [m]mgen addr
|
||||
(Type 'q' to quit sorting): """).strip()
|
||||
if reply == 'a': unspent.sort(s_amt); sort = "amount"; break
|
||||
elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
|
||||
elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
|
||||
elif reply == 'A': unspent.sort(s_age); sort = "age"; break
|
||||
elif reply == 'M': unspent.sort(s_mmgen); mmaddr,sort=True,"mmgen"; break
|
||||
elif reply == 'r':
|
||||
reverse = False if reverse else True
|
||||
unspent.reverse()
|
||||
break
|
||||
elif reply == 'g': group = False if group else True; break
|
||||
elif reply == 'm': mmaddr = False if mmaddr else True; break
|
||||
elif reply == 'q': break
|
||||
else: msg("Invalid input")
|
||||
|
||||
msg("\n")
|
||||
if reply == 'q': break
|
||||
|
||||
return unspent
|
||||
return tuple(unspent)
|
||||
|
||||
|
||||
def verify_mmgen_label(s,return_str=False):
|
||||
|
||||
fail = "" if return_str else False
|
||||
success = s if return_str else True
|
||||
|
||||
if not s: return fail
|
||||
|
||||
label = s.split()[0]
|
||||
if label[8] != ':': return fail
|
||||
for i in label[:8]:
|
||||
if not i in "01234567890ABCDEF": return fail
|
||||
for i in label[9:]:
|
||||
if not i in "0123456789": return fail
|
||||
|
||||
return success
|
||||
|
||||
|
||||
def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
|
||||
|
|
@ -226,10 +256,11 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
|
|||
msg(" " + """
|
||||
%-2s tx,vout: %s,%s
|
||||
address: %s
|
||||
label: %s
|
||||
amount: %s BTC
|
||||
confirmations: %s (around %s days)
|
||||
""".strip() %
|
||||
(n+1,i['txid'],i['vout'],j['address'],
|
||||
(n+1,i['txid'],i['vout'],j['address'],verify_mmgen_label(j['account'],True),
|
||||
trim_exponent(j['amount']),j['confirmations'],days)+"\n")
|
||||
break
|
||||
|
||||
|
|
@ -251,7 +282,7 @@ def view_tx_data(c,inputs_data,tx_hex,metadata=[]):
|
|||
msg("TX fee: %s BTC\n" % trim_exponent(total_in-total_out))
|
||||
|
||||
|
||||
def parse_tx_data(tx_data):
|
||||
def parse_tx_data(tx_data,infile):
|
||||
|
||||
if len(tx_data) != 4:
|
||||
msg("'%s': not a transaction file" % infile)
|
||||
|
|
@ -288,7 +319,7 @@ def select_outputs(unspent,prompt):
|
|||
while True:
|
||||
reply = my_raw_input(prompt).strip()
|
||||
if reply:
|
||||
selected = ()
|
||||
selected = []
|
||||
try:
|
||||
selected = [int(i) - 1 for i in reply.split()]
|
||||
except: pass
|
||||
|
|
@ -302,7 +333,7 @@ def select_outputs(unspent,prompt):
|
|||
|
||||
msg("'%s': Invalid input" % reply)
|
||||
|
||||
return [unspent[i] for i in selected]
|
||||
return list(set(selected))
|
||||
|
||||
|
||||
def make_tx_out(rcpt_arg):
|
||||
|
|
@ -323,3 +354,18 @@ def make_tx_out(rcpt_arg):
|
|||
sys.exit(3)
|
||||
|
||||
return tx_out
|
||||
|
||||
def check_wallet_addr_label(label):
|
||||
|
||||
if len(label) > 16:
|
||||
msg("'%s': illegal label (length must be <= 16 characters)" % label)
|
||||
sys.exit(3)
|
||||
|
||||
from string import ascii_letters, digits
|
||||
chrs = tuple(ascii_letters + digits) + wallet_addr_label_symbols
|
||||
for ch in list(label):
|
||||
if ch not in chrs:
|
||||
msg("'%s': illegal character in label '%s'" % (ch,label))
|
||||
msg("Permitted characters: A-Za-z0-9, plus '%s'" %
|
||||
"', '".join(wallet_addr_label_symbols))
|
||||
sys.exit(3)
|
||||
|
|
|
|||
|
|
@ -250,7 +250,7 @@ def make_chksum_8(s):
|
|||
from hashlib import sha256
|
||||
return sha256(sha256(s).digest()).hexdigest()[:8].upper()
|
||||
|
||||
def _make_chksum_6(s):
|
||||
def make_chksum_6(s):
|
||||
from hashlib import sha256
|
||||
return sha256(s).hexdigest()[:6]
|
||||
|
||||
|
|
@ -438,7 +438,7 @@ def write_seed(seed, opts):
|
|||
|
||||
from mmgen.bitcoin import b58encode_pad
|
||||
data = col4(b58encode_pad(seed))
|
||||
chk = _make_chksum_6(b58encode_pad(seed))
|
||||
chk = make_chksum_6(b58encode_pad(seed))
|
||||
|
||||
o = "%s %s\n" % (chk,data)
|
||||
|
||||
|
|
@ -522,11 +522,11 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
|||
label,
|
||||
"{} {} {} {} {}".format(*metadata),
|
||||
"{}: {} {} {}".format(hash_preset,*_get_hash_params(hash_preset)),
|
||||
"{} {}".format(_make_chksum_6(sf), col4(sf)),
|
||||
"{} {}".format(_make_chksum_6(esf), col4(esf))
|
||||
"{} {}".format(make_chksum_6(sf), col4(sf)),
|
||||
"{} {}".format(make_chksum_6(esf), col4(esf))
|
||||
)
|
||||
|
||||
chk = _make_chksum_6(" ".join(lines))
|
||||
chk = make_chksum_6(" ".join(lines))
|
||||
|
||||
confirm = False if 'quiet' in opts else True
|
||||
write_to_file(outfile, "\n".join((chk,)+lines)+"\n", confirm)
|
||||
|
|
@ -593,7 +593,7 @@ def check_wallet_format(infile, lines, opts):
|
|||
|
||||
|
||||
def _check_chksum_6(chk,val,desc,infile):
|
||||
comp_chk = _make_chksum_6(val)
|
||||
comp_chk = make_chksum_6(val)
|
||||
if chk != comp_chk:
|
||||
msg("%s checksum incorrect in file '%s'!" % (desc,infile))
|
||||
msg("Checksum: %s. Computed value: %s" % (chk,comp_chk))
|
||||
|
|
@ -682,7 +682,7 @@ def get_seed_from_seed_data(words):
|
|||
stored_chk = words[0]
|
||||
seed_b58 = "".join(words[1:])
|
||||
|
||||
chk = _make_chksum_6(seed_b58)
|
||||
chk = make_chksum_6(seed_b58)
|
||||
msg_r("Validating %s checksum..." % seed_ext)
|
||||
|
||||
if compare_checksums(chk, "from seed", stored_chk, "from input"):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue