Signing with multiple wallets/seed sources
This commit is contained in:
parent
caef53909e
commit
faaf0bab77
7 changed files with 122 additions and 67 deletions
|
|
@ -82,7 +82,15 @@ c = connect_to_bitcoind()
|
|||
|
||||
if not 'quiet' in opts and not 'info' in opts: do_license_msg()
|
||||
|
||||
unspent = sort_and_view(c.listunspent())
|
||||
# Begin test
|
||||
# import mmgen.rpc
|
||||
# us = eval(get_data_from_file("listunspent.json"))
|
||||
# End test
|
||||
|
||||
us = c.listunspent()
|
||||
# write_to_file("listunspent.json",repr(us))
|
||||
# sys.exit()
|
||||
unspent = sort_and_view(us)
|
||||
|
||||
total = trim_exponent(sum([i.amount for i in unspent]))
|
||||
|
||||
|
|
|
|||
108
mmgen-txsign
108
mmgen-txsign
|
|
@ -31,7 +31,7 @@ from mmgen.utils import *
|
|||
help_data = {
|
||||
'prog_name': sys.argv[0].split("/")[-1],
|
||||
'desc': "Sign a Bitcoin transaction generated by mmgen-txcreate",
|
||||
'usage': "[opts] <transaction file> [mmgen wallet/seed/mnemonic file]",
|
||||
'usage': "[opts] <transaction file> [mmgen wallet/seed/words/brain file]...",
|
||||
'options': """
|
||||
-h, --help Print this help message
|
||||
-d, --outdir d Specify an alternate directory 'd' for output
|
||||
|
|
@ -59,8 +59,15 @@ 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 wallet.dat and use the '--force-wallet-dat' option.
|
||||
""".format(seed_ext)
|
||||
required mmgen keys into the bitcoind wallet.dat and use the
|
||||
'--force-wallet-dat' option.
|
||||
|
||||
Seed data supplied in files must have the following extensions:
|
||||
wallet: '.{}'
|
||||
seed: '.{}'
|
||||
mnemonic: '.{}'
|
||||
brainwallet: '.{}'
|
||||
""".format(seed_ext,wallet_ext,seed_ext,mn_ext,brain_ext)
|
||||
}
|
||||
|
||||
short_opts = "hd:efik:qb:ms"
|
||||
|
|
@ -77,15 +84,15 @@ if debug:
|
|||
print "Processed options: %s" % repr(opts)
|
||||
print "Cmd args: %s" % repr(cmd_args)
|
||||
|
||||
if len(cmd_args) in (1,2):
|
||||
tx_file = cmd_args[0]
|
||||
check_infile(tx_file)
|
||||
if len(cmd_args) > 0:
|
||||
for i in cmd_args: check_infile(i)
|
||||
else: usage(help_data)
|
||||
|
||||
# Begin execution
|
||||
|
||||
c = connect_to_bitcoind()
|
||||
|
||||
tx_file = cmd_args.pop(0)
|
||||
tx_data = get_lines_from_file(tx_file,"transaction data")
|
||||
|
||||
metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
|
||||
|
|
@ -101,25 +108,8 @@ 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)
|
||||
|
||||
|
||||
def sign_transaction(tx_hex,sig_data,keys=None):
|
||||
|
||||
from mmgen.rpc import exceptions
|
||||
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
|
||||
except exceptions.InvalidAddressOrKey:
|
||||
msg("Invalid address or key")
|
||||
sys.exit(3)
|
||||
# except:
|
||||
# msg("Failed to sign transaction")
|
||||
# sys.exit(3)
|
||||
|
||||
return sig_tx
|
||||
|
||||
|
||||
# Are inputs mmgen addresses?
|
||||
infile,mmgen_addrs,other_addrs = "",[],[]
|
||||
infile,mmgen_addrs,other_addrs,keys = "",[],[],[]
|
||||
|
||||
for i in inputs_data:
|
||||
if verify_mmgen_label(i['account']):
|
||||
|
|
@ -129,32 +119,46 @@ for i in inputs_data:
|
|||
|
||||
if mmgen_addrs and not 'force_wallet_dat' in opts:
|
||||
# Check that all the seed IDs are the same:
|
||||
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)
|
||||
seed_ids = list(set([i['account'][:8] for i in mmgen_addrs]))
|
||||
while seed_ids:
|
||||
if cmd_args:
|
||||
infile = cmd_args.pop()
|
||||
ext = infile.split(".")[-1]
|
||||
for e,o in (
|
||||
(wallet_ext, {}),
|
||||
(mn_ext, {"from_mnemonic":True}),
|
||||
(seed_ext, {"from_seed": True}),
|
||||
(brain_ext, {})
|
||||
):
|
||||
if e == ext:
|
||||
if e == brain_ext:
|
||||
if "from_brain" in opts: o = opts
|
||||
else:
|
||||
msg("'--from-brain' option must be specified for brainwallet file")
|
||||
sys.exit(2)
|
||||
seed = get_seed(infile,o)
|
||||
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("",opts)
|
||||
else:
|
||||
msg("One of '-b', '-m' or '-s' must be specified for seed IDs: %s" %
|
||||
" ".join(seed_ids))
|
||||
sys.exit(2)
|
||||
|
||||
if len(cmd_args) == 2:
|
||||
infile = cmd_args[1]
|
||||
elif "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)
|
||||
seed_id = make_chksum_8(seed)
|
||||
if seed_id in seed_ids:
|
||||
seed_ids.remove(seed_id)
|
||||
seed_id_addrs = []
|
||||
for i in mmgen_addrs:
|
||||
if i['account'][:8] == seed_id:
|
||||
seed_id_addrs.append(int(i['account'].split()[0][9:]))
|
||||
|
||||
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)]
|
||||
from mmgen.addr import generate_addrs
|
||||
o = {'no_addresses': True, 'gen_what': "keys"}
|
||||
keys += [i['wif'] for i in generate_addrs(seed, seed_id_addrs, o)]
|
||||
else:
|
||||
msg("Supplied seed ID (%s) is incorrect" % seed_id)
|
||||
sys.exit(2)
|
||||
|
||||
if other_addrs:
|
||||
if 'keys_from_file' in opts:
|
||||
|
|
@ -169,12 +173,12 @@ address%s: %s""" % (
|
|||
))
|
||||
sys.exit(2)
|
||||
|
||||
sig_tx = sign_transaction(tx_hex,sig_data,keys)
|
||||
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
|
||||
|
||||
elif 'keys_from_file' in opts:
|
||||
keys = get_lines_from_file(opts['keys_from_file'],"key data")
|
||||
|
||||
sig_tx = sign_transaction(tx_hex,sig_data,keys)
|
||||
sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
|
||||
else:
|
||||
prompt = "Enter passphrase for bitcoind wallet: "
|
||||
if 'echo_passphrase' in opts:
|
||||
|
|
@ -198,7 +202,7 @@ else:
|
|||
else:
|
||||
msg("Passphrase OK")
|
||||
|
||||
sig_tx = sign_transaction(tx_hex,sig_data)
|
||||
sig_tx = sign_transaction(c,tx_hex,sig_data)
|
||||
|
||||
if wallet_enc:
|
||||
c.walletlock()
|
||||
|
|
@ -207,7 +211,7 @@ else:
|
|||
if sig_tx['complete']:
|
||||
msg("Signing completed")
|
||||
else:
|
||||
msg("Not all keys could be found to sign this transaction")
|
||||
msg("Some keys were missing. Transaction could not be signed.")
|
||||
sys.exit(3)
|
||||
|
||||
prompt = "Save signed transaction?"
|
||||
|
|
|
|||
|
|
@ -107,7 +107,9 @@ def generate_addrs(seed, addrnums, opts):
|
|||
|
||||
out.append(el)
|
||||
|
||||
sys.stderr.write("\rGenerated %s %s%s\n"%(t_addrs,opts['gen_what']," "*15))
|
||||
w = opts['gen_what']
|
||||
if t_addrs == 1: w = w[:-1]
|
||||
sys.stderr.write("\rGenerated %s %s%s\n"%(t_addrs, w, " "*15))
|
||||
|
||||
return out
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,10 @@
|
|||
config.py: Constants and configuration options for the mmgen suite
|
||||
"""
|
||||
proj_name = "mmgen"
|
||||
wallet_ext = "mmdat"
|
||||
seed_ext = "mmseed"
|
||||
mn_ext = "mmwords"
|
||||
brain_ext = "mmbrain"
|
||||
default_wl = "electrum"
|
||||
#default_wl = "tirosh"
|
||||
|
||||
|
|
|
|||
|
|
@ -91,11 +91,16 @@ class AuthServiceProxy(object):
|
|||
'method': self.__serviceName,
|
||||
'params': args,
|
||||
'id': self.__idcnt})
|
||||
self.__conn.request('POST', self.__url.path, postdata,
|
||||
{ 'Host' : self.__url.hostname,
|
||||
'User-Agent' : USER_AGENT,
|
||||
'Authorization' : self.__authhdr,
|
||||
'Content-type' : 'application/json' })
|
||||
try:
|
||||
self.__conn.request('POST', self.__url.path, postdata,
|
||||
{ 'Host' : self.__url.hostname,
|
||||
'User-Agent' : USER_AGENT,
|
||||
'Authorization' : self.__authhdr,
|
||||
'Content-type' : 'application/json' })
|
||||
except:
|
||||
print "Unable to connect to bitcoind. Exiting"
|
||||
import sys
|
||||
sys.exit(2)
|
||||
|
||||
httpresp = self.__conn.getresponse()
|
||||
if httpresp is None:
|
||||
|
|
|
|||
33
mmgen/tx.py
33
mmgen/tx.py
|
|
@ -120,13 +120,13 @@ 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_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])
|
||||
|
|
@ -211,7 +211,7 @@ def sort_and_view(unspent):
|
|||
"""\n
|
||||
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()
|
||||
(Type 'q' to quit sorting, 'p' to print to file): """).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
|
||||
|
|
@ -223,11 +223,16 @@ View options: [g]roup, show [m]mgen addr
|
|||
break
|
||||
elif reply == 'g': group = False if group else True; break
|
||||
elif reply == 'm': mmaddr = False if mmaddr else True; break
|
||||
elif reply == 'p':
|
||||
f = "listunspent.out"
|
||||
write_to_file(f,"\n".join(output)+"\n")
|
||||
msg("\nData written to '%s'" % f)
|
||||
sys.exit(1)
|
||||
elif reply == 'q': break
|
||||
else: msg("Invalid input")
|
||||
|
||||
msg("\n")
|
||||
if reply == 'q': break
|
||||
if reply in 'q': break
|
||||
|
||||
return tuple(unspent)
|
||||
|
||||
|
|
@ -426,3 +431,23 @@ def parse_addrs_file(f):
|
|||
return seed_id,ret
|
||||
|
||||
sys.exit(3)
|
||||
|
||||
|
||||
def sign_transaction(c,tx_hex,sig_data,keys=None):
|
||||
|
||||
if keys:
|
||||
msg("%s keys total" % len(keys))
|
||||
if debug: print "Keys:\n %s" % "\n ".join(keys)
|
||||
|
||||
from mmgen.rpc import exceptions
|
||||
|
||||
try:
|
||||
sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
|
||||
except exceptions.InvalidAddressOrKey:
|
||||
msg("Invalid address or key")
|
||||
sys.exit(3)
|
||||
# except:
|
||||
# msg("Failed to sign transaction")
|
||||
# sys.exit(3)
|
||||
|
||||
return sig_tx
|
||||
|
|
|
|||
|
|
@ -473,7 +473,7 @@ def write_seed(seed, opts):
|
|||
|
||||
def write_mnemonic(mn, seed, opts):
|
||||
|
||||
outfile = "%s.words" % make_chksum_8(seed).upper()
|
||||
outfile = "%s.%s" % (make_chksum_8(seed).upper(),mn_ext)
|
||||
if 'outdir' in opts:
|
||||
outfile = "%s/%s" % (opts['outdir'], outfile)
|
||||
|
||||
|
|
@ -524,7 +524,7 @@ def write_wallet_to_file(seed, passwd, key_id, salt, enc_seed, opts):
|
|||
|
||||
hash_preset = opts['hash_preset']
|
||||
|
||||
outfile = "{}-{}[{},{}].dat".format(seed_id,key_id,seed_len,hash_preset)
|
||||
outfile="{}-{}[{},{}].{}".format(seed_id,key_id,seed_len,hash_preset,wallet_ext)
|
||||
if 'outdir' in opts:
|
||||
outfile = "%s/%s" % (opts['outdir'], outfile)
|
||||
|
||||
|
|
@ -686,6 +686,14 @@ def get_lines_from_file(infile,what):
|
|||
return lines
|
||||
|
||||
|
||||
def get_data_from_file(infile,what="data"):
|
||||
msg("Getting %s from file '%s'" % (what,infile))
|
||||
f = open_file_or_exit(infile,'r')
|
||||
data = f.read()
|
||||
f.close()
|
||||
return data
|
||||
|
||||
|
||||
def get_words(infile,what,prompt,opts):
|
||||
if infile:
|
||||
words = _get_words_from_file(infile,what)
|
||||
|
|
@ -785,7 +793,6 @@ def get_seed(infile,opts,no_wallet=False):
|
|||
from mmgen.mnemonic import get_seed_from_mnemonic
|
||||
return get_seed_from_mnemonic(words,wl)
|
||||
elif 'from_brain' in opts:
|
||||
msg("")
|
||||
if 'quiet' not in opts:
|
||||
confirm_or_exit(
|
||||
cmessages['brain_warning'].format(
|
||||
|
|
@ -804,6 +811,7 @@ def get_seed(infile,opts,no_wallet=False):
|
|||
else:
|
||||
return get_seed_from_wallet(infile, opts)
|
||||
|
||||
|
||||
def remove_blanks_comments(lines):
|
||||
import re
|
||||
# re.sub(pattern, repl, string, count=0, flags=0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue