Signing with multiple wallets/seed sources

This commit is contained in:
philemon 2014-01-07 19:53:12 +04:00
commit faaf0bab77
7 changed files with 122 additions and 67 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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