Browse Source

Signing with multiple wallets/seed sources

philemon 11 years ago
parent
commit
faaf0bab77
7 changed files with 122 additions and 67 deletions
  1. 9 1
      mmgen-txcreate
  2. 57 53
      mmgen-txsign
  3. 3 1
      mmgen/addr.py
  4. 3 0
      mmgen/config.py
  5. 10 5
      mmgen/rpc/proxy.py
  6. 29 4
      mmgen/tx.py
  7. 11 3
      mmgen/utils.py

+ 9 - 1
mmgen-txcreate

@@ -82,7 +82,15 @@ c = connect_to_bitcoind()
 
 
 if not 'quiet' in opts and not 'info' in opts: do_license_msg()
 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]))
 total = trim_exponent(sum([i.amount for i in unspent]))
 
 

+ 57 - 53
mmgen-txsign

@@ -31,7 +31,7 @@ from mmgen.utils import *
 help_data = {
 help_data = {
 	'prog_name': sys.argv[0].split("/")[-1],
 	'prog_name': sys.argv[0].split("/")[-1],
 	'desc':    "Sign a Bitcoin transaction generated by mmgen-txcreate",
 	'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': """
 	'options': """
 -h, --help               Print this help message
 -h, --help               Print this help message
 -d, --outdir          d  Specify an alternate directory 'd' for output
 -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
 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)
 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
 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"
 short_opts = "hd:efik:qb:ms"
@@ -77,15 +84,15 @@ if debug:
 	print "Processed options:     %s" % repr(opts)
 	print "Processed options:     %s" % repr(opts)
 	print "Cmd args:              %s" % repr(cmd_args)
 	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)
 else: usage(help_data)
 
 
 # Begin execution
 # Begin execution
 
 
 c = connect_to_bitcoind()
 c = connect_to_bitcoind()
 
 
+tx_file = cmd_args.pop(0)
 tx_data = get_lines_from_file(tx_file,"transaction data")
 tx_data = get_lines_from_file(tx_file,"transaction data")
 
 
 metadata,tx_hex,sig_data,inputs_data = parse_tx_data(tx_data,tx_file)
 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):
 if user_confirm("View transaction data? ",default_yes=False):
 	view_tx_data(c,inputs_data,tx_hex,metadata)
 	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?
 # Are inputs mmgen addresses?
-infile,mmgen_addrs,other_addrs = "",[],[]
+infile,mmgen_addrs,other_addrs,keys = "",[],[],[]
 
 
 for i in inputs_data:
 for i in inputs_data:
 	if verify_mmgen_label(i['account']):
 	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:
 if mmgen_addrs and not 'force_wallet_dat' in opts:
 	# Check that all the seed IDs are the same:
 	# 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)
-
-	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    = 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]
+	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)
 
 
-	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)]
+		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:]))
+
+			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 other_addrs:
 		if 'keys_from_file' in opts:
 		if 'keys_from_file' in opts:
@@ -169,12 +173,12 @@ address%s: %s""" % (
 	  ))
 	  ))
 			sys.exit(2)
 			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:
 elif 'keys_from_file' in opts:
 	keys = get_lines_from_file(opts['keys_from_file'],"key data")
 	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:
 else:
 	prompt = "Enter passphrase for bitcoind wallet: "
 	prompt = "Enter passphrase for bitcoind wallet: "
 	if 'echo_passphrase' in opts:
 	if 'echo_passphrase' in opts:
@@ -198,7 +202,7 @@ else:
 	else:
 	else:
 		msg("Passphrase OK")
 		msg("Passphrase OK")
 
 
-	sig_tx = sign_transaction(tx_hex,sig_data)
+	sig_tx = sign_transaction(c,tx_hex,sig_data)
 
 
 	if wallet_enc:
 	if wallet_enc:
 		c.walletlock()
 		c.walletlock()
@@ -207,7 +211,7 @@ else:
 if sig_tx['complete']:
 if sig_tx['complete']:
 	msg("Signing completed")
 	msg("Signing completed")
 else:
 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)
 	sys.exit(3)
 
 
 prompt = "Save signed transaction?"
 prompt = "Save signed transaction?"

+ 3 - 1
mmgen/addr.py

@@ -107,7 +107,9 @@ def generate_addrs(seed, addrnums, opts):
 
 
 		out.append(el)
 		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
 	return out
 
 

+ 3 - 0
mmgen/config.py

@@ -19,7 +19,10 @@
 config.py:  Constants and configuration options for the mmgen suite
 config.py:  Constants and configuration options for the mmgen suite
 """
 """
 proj_name     = "mmgen"
 proj_name     = "mmgen"
+wallet_ext    = "mmdat"
 seed_ext      = "mmseed"
 seed_ext      = "mmseed"
+mn_ext        = "mmwords"
+brain_ext     = "mmbrain"
 default_wl    = "electrum"
 default_wl    = "electrum"
 #default_wl    = "tirosh"
 #default_wl    = "tirosh"
 
 

+ 10 - 5
mmgen/rpc/proxy.py

@@ -91,11 +91,16 @@ class AuthServiceProxy(object):
                 'method': self.__serviceName,
                 'method': self.__serviceName,
                 'params': args,
                 'params': args,
                 'id': self.__idcnt})
                 '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()
          httpresp = self.__conn.getresponse()
          if httpresp is None:
          if httpresp is None:

+ 29 - 4
mmgen/tx.py

@@ -120,13 +120,13 @@ def get_cfg_options(cfg_keys):
 
 
 
 
 def print_tx_to_file(tx,sel_unspent,send_amt,opts):
 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()
 	tx_id = make_chksum_6(unhexlify(tx)).upper()
 	outfile = "tx_%s[%s].raw" % (tx_id,send_amt)
 	outfile = "tx_%s[%s].raw" % (tx_id,send_amt)
 	if 'outdir' in opts:
 	if 'outdir' in opts:
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 	metadata = "%s %s %s" % (tx_id, send_amt, make_timestamp())
 	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" % (
 	data = "%s\n%s\n%s\n%s\n" % (
 			metadata, tx, repr(sig_data),
 			metadata, tx, repr(sig_data),
 			repr([i.__dict__ for i in sel_unspent])
 			repr([i.__dict__ for i in sel_unspent])
@@ -211,7 +211,7 @@ def sort_and_view(unspent):
 """\n
 """\n
 Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
 Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
 View options: [g]roup, show [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
 			if   reply == 'a': unspent.sort(s_amt);  sort = "amount"; break
 			elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
 			elif reply == 't': unspent.sort(s_txid); sort = "txid"; break
 			elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
 			elif reply == 'd': unspent.sort(s_addr); sort = "address"; break
@@ -223,11 +223,16 @@ View options: [g]roup, show [m]mgen addr
 				break
 				break
 			elif reply == 'g': group = False if group else True; break
 			elif reply == 'g': group = False if group else True; break
 			elif reply == 'm': mmaddr = False if mmaddr 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
 			elif reply == 'q': break
 			else: msg("Invalid input")
 			else: msg("Invalid input")
 
 
 		msg("\n")
 		msg("\n")
-		if reply == 'q': break
+		if reply in 'q': break
 
 
 	return tuple(unspent)
 	return tuple(unspent)
 
 
@@ -426,3 +431,23 @@ def parse_addrs_file(f):
 		return seed_id,ret
 		return seed_id,ret
 
 
 	sys.exit(3)
 	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

+ 11 - 3
mmgen/utils.py

@@ -473,7 +473,7 @@ def write_seed(seed, opts):
 
 
 def write_mnemonic(mn, 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:
 	if 'outdir' in opts:
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 		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']
 	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:
 	if 'outdir' in opts:
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 		outfile = "%s/%s" % (opts['outdir'], outfile)
 
 
@@ -686,6 +686,14 @@ def get_lines_from_file(infile,what):
 	return lines
 	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):
 def get_words(infile,what,prompt,opts):
 	if infile:
 	if infile:
 		words = _get_words_from_file(infile,what)
 		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
 		from mmgen.mnemonic import get_seed_from_mnemonic
 		return get_seed_from_mnemonic(words,wl)
 		return get_seed_from_mnemonic(words,wl)
 	elif 'from_brain' in opts:
 	elif 'from_brain' in opts:
-		msg("")
 		if 'quiet' not in opts:
 		if 'quiet' not in opts:
 			confirm_or_exit(
 			confirm_or_exit(
 				cmessages['brain_warning'].format(
 				cmessages['brain_warning'].format(
@@ -804,6 +811,7 @@ def get_seed(infile,opts,no_wallet=False):
 	else:
 	else:
 		return get_seed_from_wallet(infile, opts)
 		return get_seed_from_wallet(infile, opts)
 
 
+
 def remove_blanks_comments(lines):
 def remove_blanks_comments(lines):
 	import re
 	import re
 #	re.sub(pattern, repl, string, count=0, flags=0)
 #	re.sub(pattern, repl, string, count=0, flags=0)