tx.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C) 2013-2014 by philemon <mmgen-py@yandex.com>
  5. #
  6. # This program is free software: you can redistribute it and/or modify
  7. # it under the terms of the GNU General Public License as published by
  8. # the Free Software Foundation, either version 3 of the License, or
  9. # (at your option) any later version.
  10. #
  11. # This program is distributed in the hope that it will be useful,
  12. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. # GNU General Public License for more details.
  15. #
  16. # You should have received a copy of the GNU General Public License
  17. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  18. """
  19. tx.py: Bitcoin transaction routines
  20. """
  21. from binascii import unhexlify
  22. from mmgen.util import *
  23. import sys, os
  24. from decimal import Decimal
  25. import mmgen.config as g
  26. txmsg = {
  27. 'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
  28. 'throwaway_change': """
  29. ERROR: This transaction produces change (%s BTC); however, no change
  30. address was specified.
  31. """.strip(),
  32. 'mixed_inputs': """
  33. NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs,
  34. which makes the signing process more complicated. When signing the
  35. transaction, keys for the non-mmgen inputs must be supplied in a separate
  36. file using the '-k' option to mmgen-txsign.
  37. Selected mmgen inputs: %s"""
  38. }
  39. # Deleted text:
  40. # Alternatively, you may import the mmgen keys into the wallet.dat of your
  41. # offline bitcoind, first generating the required keys with mmgen-keygen and
  42. # then running mmgen-txsign with the '-f' option to force the use of
  43. # wallet.dat as the key source.
  44. def connect_to_bitcoind():
  45. host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
  46. cfg = get_bitcoind_cfg_options((user,passwd))
  47. import mmgen.rpc.connection
  48. f = mmgen.rpc.connection.BitcoinConnection
  49. try:
  50. c = f(cfg[user],cfg[passwd],host,port)
  51. except:
  52. msg("Unable to establish RPC connection with bitcoind")
  53. sys.exit(2)
  54. return c
  55. def trim_exponent(n):
  56. '''Remove exponent and trailing zeros.
  57. '''
  58. d = Decimal(n)
  59. return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
  60. def is_btc_amt(amt):
  61. from decimal import Decimal
  62. try:
  63. ret = Decimal(amt)
  64. except:
  65. msg("%s: Invalid amount" % amt)
  66. return False
  67. if g.debug:
  68. print "Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple()))
  69. if ret.as_tuple()[-1] < -8:
  70. msg("%s: Too many decimal places in amount" % amt)
  71. return False
  72. if ret == 0:
  73. msg("Requested zero BTC amount")
  74. return False
  75. return trim_exponent(ret)
  76. def check_btc_amt(amt):
  77. ret = is_btc_amt(amt)
  78. if ret:
  79. return ret
  80. else:
  81. sys.exit(3)
  82. def get_bitcoind_cfg_options(cfg_keys):
  83. if "HOME" in os.environ:
  84. data_dir = ".bitcoin"
  85. cfg_file = "%s/%s/%s" % (os.environ["HOME"], data_dir, "bitcoin.conf")
  86. elif "HOMEPATH" in os.environ:
  87. # Windows:
  88. data_dir = r"Application Data\Bitcoin"
  89. cfg_file = "%s\%s\%s" % (os.environ["HOMEPATH"],data_dir,"bitcoin.conf")
  90. else:
  91. msg("Neither $HOME nor %HOMEPATH% are set")
  92. msg("Don't know where to look for 'bitcoin.conf'")
  93. sys.exit(3)
  94. try:
  95. f = open(cfg_file)
  96. except:
  97. msg("Unable to open file '%s' for reading" % cfg_file)
  98. sys.exit(2)
  99. cfg = {}
  100. for line in f.readlines():
  101. s = line.translate(None,"\n\t ").split("=")
  102. for k in cfg_keys:
  103. if s[0] == k: cfg[k] = s[1]
  104. f.close()
  105. for k in cfg_keys:
  106. if not k in cfg:
  107. msg("Configuration option '%s' must be set in %s" % (k,cfg_file))
  108. sys.exit(2)
  109. return cfg
  110. def print_tx_to_file(tx,sel_unspent,send_amt,b2m_map,opts):
  111. tx_id = make_chksum_6(unhexlify(tx)).upper()
  112. outfile = "tx_%s[%s].raw" % (tx_id,send_amt)
  113. if 'outdir' in opts:
  114. outfile = "%s/%s" % (opts['outdir'], outfile)
  115. metadata = "%s %s %s" % (tx_id, send_amt, make_timestamp())
  116. data = "%s\n%s\n%s\n%s\n" % (
  117. metadata, tx,
  118. repr([i.__dict__ for i in sel_unspent]),
  119. repr(b2m_map)
  120. )
  121. write_to_file(outfile,data,confirm=False)
  122. msg("Transaction data saved to file '%s'" % outfile)
  123. def print_signed_tx_to_file(tx,sig_tx,metadata,opts):
  124. tx_id = make_chksum_6(unhexlify(tx)).upper()
  125. outfile = "tx_{}[{}].sig".format(*metadata[:2])
  126. if 'outdir' in opts:
  127. outfile = "%s/%s" % (opts['outdir'], outfile)
  128. data = "%s\n%s\n" % (" ".join(metadata),sig_tx)
  129. write_to_file(outfile,data,confirm=False)
  130. msg("Signed transaction saved to file '%s'" % outfile)
  131. def print_sent_tx_to_file(tx,metadata,opts):
  132. outfile = "tx_{}[{}].out".format(*metadata[:2])
  133. if 'outdir' in opts:
  134. outfile = "%s/%s" % (opts['outdir'], outfile)
  135. write_to_file(outfile,tx+"\n",confirm=False)
  136. msg("Transaction ID saved to file '%s'" % outfile)
  137. def format_unspent_outputs_for_printing(out,sort_info,total):
  138. pfs = " %-4s %-67s %-34s %-12s %-13s %-10s %s"
  139. pout = [pfs % ("Num","TX id,Vout","Address","MMgen ID",
  140. "Amount (BTC)","Age (days)", "Comment")]
  141. for n,i in enumerate(out):
  142. addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address
  143. tx = " " * 63 + "=" \
  144. if i.skip == "txid" and "grouped" in sort_info else str(i.txid)
  145. s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,i.mmid,i.amt,i.days,i.label)
  146. pout.append(s.rstrip())
  147. return \
  148. "Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format(
  149. make_timestr(), " ".join(sort_info), "\n".join(pout), total
  150. )
  151. def sort_and_view(unspent):
  152. def s_amt(i): return i.amount
  153. def s_txid(i): return "%s %03s" % (i.txid,i.vout)
  154. def s_addr(i): return i.address
  155. def s_age(i): return i.confirmations
  156. def s_mmgen(i): return i.account
  157. sort,group,show_mmaddr,reverse = "",False,False,False
  158. total = trim_exponent(sum([i.amount for i in unspent]))
  159. hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s"
  160. options_msg = """
  161. Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
  162. Display options: [g]roup, show [m]mgen addr, r[e]draw screen
  163. """.strip()
  164. prompt = \
  165. "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
  166. from copy import deepcopy
  167. print_to_file_msg = ""
  168. msg("")
  169. from mmgen.term import get_terminal_size
  170. max_acct_len = max([len(i.account) for i in unspent])
  171. while True:
  172. cols = get_terminal_size()[0]
  173. if cols < g.min_screen_width:
  174. msg("mmgen-txcreate requires a screen at least %s characters wide" %
  175. g.min_screen_width)
  176. sys.exit(2)
  177. addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
  178. tx_w = max(11,min(64, cols-addr_w-32))
  179. fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s"
  180. table_hdr = fs % ("Num","TX id Vout","","Address",
  181. "Amount (BTC)","Age(d)")
  182. unsp = deepcopy(unspent)
  183. for i in unsp: i.skip = ""
  184. if group and (sort == "address" or sort == "txid"):
  185. for n in range(len(unsp)-1):
  186. a,b = unsp[n],unsp[n+1]
  187. if sort == "address" and a.address == b.address: b.skip = "addr"
  188. elif sort == "txid" and a.txid == b.txid: b.skip = "txid"
  189. for i in unsp:
  190. amt = str(trim_exponent(i.amount))
  191. lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
  192. i.amt = " "*lfill + amt
  193. i.days = int(i.confirmations * g.mins_per_block / (60*24))
  194. i.mmid,i.label = parse_mmgen_label(i.account)
  195. if i.skip == "addr":
  196. i.addr = "|" + "." * 33
  197. else:
  198. if show_mmaddr and i.mmid:
  199. acct_w = min(max_acct_len, max(24,int(addr_w-10)))
  200. btaddr_w = addr_w - acct_w - 1
  201. dots = ".." if btaddr_w < len(i.address) else ""
  202. i.addr = "%s%s %s" % (
  203. i.address[:btaddr_w-len(dots)],
  204. dots,
  205. i.account[:acct_w])
  206. else:
  207. i.addr = i.address
  208. dots = "..." if tx_w < 64 else ""
  209. i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
  210. else i.txid[:tx_w-len(dots)]+dots
  211. sort_info = ["reverse"] if reverse else []
  212. sort_info.append(sort if sort else "unsorted")
  213. if group and (sort == "address" or sort == "txid"):
  214. sort_info.append("grouped")
  215. out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
  216. for n,i in enumerate(unsp):
  217. out.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.days))
  218. msg("\n".join(out) +"\n\n" + print_to_file_msg + options_msg)
  219. print_to_file_msg = ""
  220. immed_chars = "atdAMrgmeqpvw"
  221. skip_prompt = False
  222. while True:
  223. reply = get_char(prompt, immed_chars=immed_chars)
  224. if reply == 'a': unspent.sort(key=s_amt); sort = "amount"
  225. elif reply == 't': unspent.sort(key=s_txid); sort = "txid"
  226. elif reply == 'd': unspent.sort(key=s_addr); sort = "address"
  227. elif reply == 'A': unspent.sort(key=s_age); sort = "age"
  228. elif reply == 'M':
  229. unspent.sort(key=s_mmgen); sort = "mmgen"
  230. show_mmaddr = True
  231. elif reply == 'r':
  232. unspent.reverse()
  233. reverse = False if reverse else True
  234. elif reply == 'g': group = False if group else True
  235. elif reply == 'm': show_mmaddr = False if show_mmaddr else True
  236. elif reply == 'e': pass
  237. elif reply == 'q': pass
  238. elif reply == 'p':
  239. data = format_unspent_outputs_for_printing(unsp,sort_info,total)
  240. outfile = "listunspent[%s].out" % ",".join(sort_info)
  241. write_to_file(outfile, data)
  242. print_to_file_msg = "Data written to '%s'\n\n" % outfile
  243. elif reply == 'v':
  244. do_pager("\n".join(out))
  245. continue
  246. elif reply == 'w':
  247. data = format_unspent_outputs_for_printing(unsp,sort_info,total)
  248. do_pager(data)
  249. continue
  250. else:
  251. msg("\nInvalid input")
  252. continue
  253. break
  254. msg("\n")
  255. if reply == 'q': break
  256. return tuple(unspent)
  257. def parse_mmgen_label(s,check_label_len=False):
  258. if not s: return "",""
  259. try: w1,w2 = s.split(None,1)
  260. except: w1,w2 = s,""
  261. if not is_mmgen_addr(w1): return "",w1
  262. if check_label_len: check_addr_label(w2)
  263. return w1,w2
  264. def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False):
  265. td = c.decoderawtransaction(tx_hex)
  266. out = "TRANSACTION DATA\n\n"
  267. if metadata:
  268. out += "Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n".format(*metadata)
  269. out += "Inputs:\n\n"
  270. total_in = 0
  271. for n,i in enumerate(td['vin']):
  272. for j in inputs_data:
  273. if j['txid'] == i['txid'] and j['vout'] == i['vout']:
  274. days = int(j['confirmations'] * g.mins_per_block / (60*24))
  275. total_in += j['amount']
  276. addr = j['address']
  277. if j['account']:
  278. tmp = j['account'].split(None,1)
  279. mmid,label = tmp if len(tmp) == 2 else (tmp[0],"")
  280. label = label or ""
  281. else:
  282. mmid,label = "",""
  283. mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
  284. for d in (
  285. (n+1, "tx,vout:", "%s,%s" % (i['txid'], i['vout'])),
  286. ("", "address:", addr + mmid_str),
  287. ("", "label:", label),
  288. ("", "amount:", "%s BTC" % trim_exponent(j['amount'])),
  289. ("", "confirmations:", "%s (around %s days)" % (j['confirmations'], days))
  290. ):
  291. if d[2]: out += ("%3s %-8s %s\n" % d)
  292. out += "\n"
  293. break
  294. total_out = 0
  295. out += "Outputs:\n\n"
  296. for n,i in enumerate(td['vout']):
  297. addr = i['scriptPubKey']['addresses'][0]
  298. mmid,label = b2m_map[addr] if addr in b2m_map else ("","")
  299. mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
  300. total_out += i['value']
  301. for d in (
  302. (n+1, "address:", addr + mmid_str),
  303. ("", "label:", label),
  304. ("", "amount:", trim_exponent(i['value']))
  305. ):
  306. if d[2]: out += ("%3s %-8s %s\n" % d)
  307. out += "\n"
  308. out += "Total input: %s BTC\n" % trim_exponent(total_in)
  309. out += "Total output: %s BTC\n" % trim_exponent(total_out)
  310. out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out)
  311. if pager: do_pager(out)
  312. else: msg("\n"+out)
  313. def parse_tx_data(tx_data,infile):
  314. if len(tx_data) != 4:
  315. msg("'%s': not a transaction file" % infile)
  316. sys.exit(2)
  317. err_fmt = "Transaction %s is invalid"
  318. if len(tx_data[0].split()) != 3:
  319. msg(err_fmt % "metadata")
  320. sys.exit(2)
  321. try: unhexlify(tx_data[1])
  322. except:
  323. msg(err_fmt % "hex data")
  324. sys.exit(2)
  325. else:
  326. if not tx_data:
  327. msg("Transaction is empty!")
  328. sys.exit(2)
  329. try:
  330. inputs_data = eval(tx_data[2])
  331. except:
  332. msg(err_fmt % "inputs data")
  333. sys.exit(2)
  334. else:
  335. if not inputs_data:
  336. msg("Transaction has no inputs!")
  337. sys.exit(2)
  338. try:
  339. map_data = eval(tx_data[3])
  340. except:
  341. msg(err_fmt % "mmgen to btc address map data")
  342. sys.exit(2)
  343. return tx_data[0].split(),tx_data[1],inputs_data,map_data
  344. def select_outputs(unspent,prompt):
  345. while True:
  346. reply = my_raw_input(prompt,allowed_chars="0123456789 -").strip()
  347. if not reply: continue
  348. from mmgen.util import parse_address_list
  349. selected = parse_address_list(reply,sep=None)
  350. if not selected: continue
  351. if selected[-1] > len(unspent):
  352. msg("Inputs must be less than %s" % len(unspent))
  353. continue
  354. return selected
  355. def is_mmgen_seed(s):
  356. import re
  357. return len(s) == 8 and re.match(r"^[0123456789ABCDEF]*$",s)
  358. def is_mmgen_num(s):
  359. import re
  360. return len(s) <= g.mmgen_idx_max_digits \
  361. and re.match(r"^[123456789]+[0123456789]*$",s)
  362. def is_mmgen_addr(s):
  363. import re
  364. return len(s) > 9 and s[8] == ':' \
  365. and re.match(r"^[0123456789ABCDEF]*$",s[:8]) \
  366. and len(s[9:]) <= g.mmgen_idx_max_digits \
  367. and re.match(r"^[123456789]+[0123456789]*$",s[9:])
  368. def is_btc_addr(s):
  369. from mmgen.bitcoin import verify_addr
  370. return verify_addr(s)
  371. def btc_addr_to_mmgen_addr(btc_addr,b2m_map):
  372. if btc_addr in b2m_map:
  373. return b2m_map[btc_addr]
  374. return "",""
  375. def mmgen_addr_to_walletd(c,mmaddr,acct_data):
  376. # We don't want to create a new object, so we'll use append()
  377. if not acct_data:
  378. for i in c.listaccounts():
  379. acct_data.append(i)
  380. for a in acct_data:
  381. if not a: continue
  382. try:
  383. w1,w2 = a.split(None,1)
  384. except:
  385. w1,w2 = a,""
  386. if w1 == mmaddr:
  387. acct = a
  388. break
  389. else:
  390. return "",""
  391. alist = c.getaddressesbyaccount(acct)
  392. if len(alist) != 1:
  393. msg("""
  394. ERROR: More than one address found for account: "%s".
  395. The tracking "wallet.dat" file appears to have been altered by a non-%s
  396. program. Please restore "wallet.dat" from a backup or create a new wallet
  397. and re-import your addresses.
  398. """.strip() % (acct,g.proj_name_cap))
  399. sys.exit(3)
  400. return alist[0],w2
  401. def mmgen_addr_to_addr_data(m,addr_data):
  402. no_data_msg = """
  403. No data found for MMgen address '%s'. Please import this address into
  404. your tracking wallet, or supply an address file for it on the command line.
  405. """.strip() % m
  406. warn_msg = """
  407. Warning: no data for address '%s' exists in the wallet, so it was
  408. taken from the user-supplied address file. You're strongly advised to
  409. import this address into your tracking wallet before proceeding with
  410. this transaction. The address will not be tracked until you do so.
  411. """.strip() % m
  412. fail_msg = """
  413. No data found for MMgen address '%s' in either wallet or supplied
  414. address file. Please import this address into your tracking wallet, or
  415. supply an address file for it on the command line.
  416. """.strip() % m
  417. ID,num = m.split(":")
  418. from binascii import unhexlify
  419. try: unhexlify(ID)
  420. except: pass
  421. else:
  422. try: num = int(num)
  423. except: pass
  424. else:
  425. if not addr_data:
  426. msg(no_data_msg)
  427. sys.exit(2)
  428. for i in addr_data:
  429. if ID == i[0]:
  430. for j in i[1]:
  431. if j[0] == num:
  432. msg(warn_msg)
  433. if not user_confirm("Continue anyway?"):
  434. sys.exit(1)
  435. return j[1],(j[2] if len(j) == 3 else "")
  436. msg(fail_msg)
  437. sys.exit(2)
  438. msg("Invalid format: %s" % m)
  439. sys.exit(3)
  440. def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts):
  441. in_maplist = [(i['account'].split()[0],i['address'])
  442. for i in inputs_data if i['account']
  443. and is_mmgen_addr(i['account'].split()[0])]
  444. out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
  445. for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
  446. if not maplist: continue
  447. qmsg("Checking MMGen -> BTC address mappings for %s" % label)
  448. mmaddrs = [i[0] for i in maplist]
  449. from copy import deepcopy
  450. pairs = get_keys_for_mmgen_addrs(mmaddrs,
  451. deepcopy(infiles),seeds,opts,gen_pairs=True)
  452. for a,b in zip(sorted(pairs),sorted(maplist)):
  453. if a != b:
  454. msg("""
  455. MMGen -> BTC address mappings differ!
  456. In transaction: %s
  457. Generated from seed: %s
  458. """.strip() % (" ".join(a)," ".join(b)))
  459. sys.exit(3)
  460. qmsg("Address mappings OK")
  461. def check_addr_label(label):
  462. if len(label) > g.max_addr_label_len:
  463. msg("'%s': overlong label (length must be <=%s)" %
  464. (label,g.max_addr_label_len))
  465. sys.exit(3)
  466. for ch in label:
  467. if ch not in g.addr_label_symbols:
  468. msg("""
  469. "%s": illegal character in label "%s".
  470. Only ASCII printable characters are permitted.
  471. """.strip() % (ch,label))
  472. sys.exit(3)
  473. def parse_addrs_file(f):
  474. lines = get_lines_from_file(f,"address data",remove_comments=True)
  475. try:
  476. seed_id,obrace = lines[0].split()
  477. except:
  478. msg("Invalid first line: '%s'" % lines[0])
  479. sys.exit(3)
  480. cbrace = lines[-1]
  481. if obrace != '{':
  482. msg("'%s': invalid first line" % lines[0])
  483. elif cbrace != '}':
  484. msg("'%s': invalid last line" % cbrace)
  485. elif not is_mmgen_seed(seed_id):
  486. msg("'%s': invalid Seed ID" % seed_id)
  487. else:
  488. ret = []
  489. for i in lines[1:-1]:
  490. d = i.split(None,2)
  491. if not is_mmgen_num(d[0]):
  492. msg("'%s': invalid address num. in line: %s" % (d[0],d))
  493. sys.exit(3)
  494. if not is_btc_addr(d[1]):
  495. msg("'%s': invalid Bitcoin address" % d[1])
  496. sys.exit(3)
  497. if len(d) == 3:
  498. check_addr_label(d[2])
  499. ret.append(tuple(d))
  500. return seed_id,ret
  501. sys.exit(3)
  502. def sign_transaction(c,tx_hex,sig_data,keys=None):
  503. if keys:
  504. qmsg("%s keys total" % len(keys))
  505. if g.debug: print "Keys:\n %s" % "\n ".join(keys)
  506. msg_r("Signing transaction...")
  507. from mmgen.rpc import exceptions
  508. try:
  509. sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
  510. except exceptions.InvalidAddressOrKey:
  511. msg("failed\nInvalid address or key")
  512. sys.exit(3)
  513. return sig_tx
  514. def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False):
  515. seed_ids = list(set([i[:8] for i in mmgen_addrs]))
  516. seed_ids_save = seed_ids[0:] # deep copy
  517. ret = []
  518. seeds_keys = [i for i in seed_ids if i in seeds]
  519. while seed_ids:
  520. if seeds_keys:
  521. seed = seeds[seeds_keys.pop(0)]
  522. else:
  523. infile = False
  524. if infiles:
  525. infile = infiles.pop(0)
  526. seed = get_seed_retry(infile,opts)
  527. elif "from_brain" in opts or "from_mnemonic" in opts \
  528. or "from_seed" in opts or "from_incog" in opts:
  529. msg("Need data for seed ID %s" % seed_ids[0])
  530. seed = get_seed_retry("",opts)
  531. else:
  532. b,p,v = ("A seed","","is") if len(seed_ids) == 1 \
  533. else ("Seed","s","are")
  534. msg("ERROR: %s source%s %s required for the following seed ID%s: %s"%
  535. (b,p,v,p," ".join(seed_ids)))
  536. sys.exit(2)
  537. seed_id = make_chksum_8(seed)
  538. if seed_id in seed_ids:
  539. seed_ids.remove(seed_id)
  540. addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
  541. seeds[seed_id] = seed
  542. from mmgen.addr import generate_keys,generate_addrs
  543. if gen_pairs:
  544. o = {"gen_what":"addresses"}
  545. ret += [("%s:%s" % (seed_id,i['num']),i['addr'])
  546. for i in generate_addrs(seed, addr_ids, o)]
  547. else:
  548. ret += [i['wif'] for i in generate_keys(seed, addr_ids)]
  549. else:
  550. if seed_id in seed_ids_save:
  551. msg_r("Ignoring duplicate seed source")
  552. if infile: msg(" '%s'" % infile)
  553. else: msg(" for ID %s" % seed_id)
  554. else:
  555. msg("Seed source produced an invalid seed ID (%s)" % seed_id)
  556. if "from_incog" in opts or infile.split(".")[-1] == g.incog_ext:
  557. msg(
  558. """Incorrect hash preset, password or incognito wallet data
  559. Trying again...""")
  560. infiles.insert(0,infile) # ugly!
  561. elif infile:
  562. msg("Invalid input file '%s'" % infile)
  563. sys.exit(2)
  564. return ret
  565. def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
  566. try:
  567. sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
  568. except:
  569. from mmgen.rpc import exceptions
  570. msg("Using keys in wallet.dat as per user request")
  571. prompt = "Enter passphrase for bitcoind wallet: "
  572. while True:
  573. passwd = get_bitcoind_passphrase(prompt,opts)
  574. try:
  575. c.walletpassphrase(passwd, 9999)
  576. except exceptions.WalletPassphraseIncorrect:
  577. msg("Passphrase incorrect")
  578. else:
  579. msg("Passphrase OK"); break
  580. sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
  581. msg("Locking wallet")
  582. try:
  583. c.walletlock()
  584. except:
  585. msg("Failed to lock wallet")
  586. return sig_tx
  587. def preverify_keys(addrs_orig, keys_orig):
  588. addrs,keys,wrong_keys = set(addrs_orig[0:]),set(keys_orig[0:]),[]
  589. if len(keys) < len(addrs):
  590. msg("ERROR: not enough keys (%s) for number of non-%s addresses (%s)" %
  591. (len(keys),g.proj_name_cap,len(addrs)))
  592. sys.exit(2)
  593. import mmgen.bitcoin as b
  594. qmsg_r('Checking that user-supplied key list contains valid keys...')
  595. invalid_keys = []
  596. for n,k in enumerate(keys,1):
  597. c = False if k[0] == '5' else True
  598. if b.wiftohex(k,compressed=c) == False:
  599. invalid_keys.append(k)
  600. if invalid_keys:
  601. s = "" if len(invalid_keys) == 1 else "s"
  602. msg("\n%s/%s invalid key%s in keylist!\n" % (len(invalid_keys),len(keys),s))
  603. sys.exit(2)
  604. else: qmsg("OK")
  605. msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
  606. try:
  607. for n,k in enumerate(keys,1):
  608. msg_r("\rkey %s of %s" % (n,len(keys)))
  609. c = False if k[0] == '5' else True
  610. hexkey = b.wiftohex(k,compressed=c)
  611. addr = b.privnum2addr(int(hexkey,16),compressed=c)
  612. if addr in addrs:
  613. addrs.remove(addr)
  614. if not addrs: break
  615. else:
  616. wrong_keys.append(k)
  617. except KeyboardInterrupt:
  618. msg("\nSkipping")
  619. else:
  620. msg("")
  621. if wrong_keys:
  622. s = "" if len(wrong_keys) == 1 else "s"
  623. msg("%s extra key%s found" % (len(wrong_keys),s))
  624. if addrs:
  625. s = "" if len(addrs) == 1 else "es"
  626. msg("No keys found for the following non-%s address%s:" %
  627. (g.proj_name_cap,s))
  628. print " %s" % "\n ".join(addrs)
  629. sys.exit(2)
  630. def missing_keys_errormsg(other_addrs):
  631. msg("""
  632. A key file must be supplied (or use the "-w" option) for the following
  633. non-mmgen address%s:
  634. """.strip() % ("" if len(other_addrs) == 1 else "es"))
  635. print " %s" % "\n ".join([i['address'] for i in other_addrs])