tx.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  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].%s" % (tx_id,send_amt,g.rawtx_ext)
  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_%s[%s].%s" % (metadata[0],metadata[1],g.sigtx_ext)
  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 %-8s %-10s %s"
  139. pout = [pfs % ("Num","TX id,Vout","Address","MMgen ID",
  140. "Amount (BTC)","Confirms","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,
  146. i.mmid,i.amt,i.confirmations,i.days,i.label)
  147. pout.append(s.rstrip())
  148. return \
  149. "Unspent outputs ({} UTC)\nSort order: {}\n\n{}\n\nTotal BTC: {}\n".format(
  150. make_timestr(), " ".join(sort_info), "\n".join(pout), total
  151. )
  152. def sort_and_view(unspent):
  153. def s_amt(i): return i.amount
  154. def s_txid(i): return "%s %03s" % (i.txid,i.vout)
  155. def s_addr(i): return i.address
  156. def s_age(i): return i.confirmations
  157. def s_mmgen(i): return i.account
  158. sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True
  159. unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default
  160. total = trim_exponent(sum([i.amount for i in unspent]))
  161. hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s"
  162. options_msg = """
  163. Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
  164. Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
  165. """.strip()
  166. prompt = \
  167. "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
  168. from copy import deepcopy
  169. print_to_file_msg = ""
  170. msg("")
  171. from mmgen.term import get_terminal_size
  172. max_acct_len = max([len(i.account) for i in unspent])
  173. while True:
  174. cols = get_terminal_size()[0]
  175. if cols < g.min_screen_width:
  176. msg("mmgen-txcreate requires a screen at least %s characters wide" %
  177. g.min_screen_width)
  178. sys.exit(2)
  179. addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
  180. tx_w = max(11,min(64, cols-addr_w-32))
  181. fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s"
  182. a = "Age(d)" if show_days else "Confirms"
  183. table_hdr = fs % ("Num","TX id Vout","","Address", "Amount (BTC)",a)
  184. unsp = deepcopy(unspent)
  185. for i in unsp: i.skip = ""
  186. if group and (sort == "address" or sort == "txid"):
  187. for n in range(len(unsp)-1):
  188. a,b = unsp[n],unsp[n+1]
  189. if sort == "address" and a.address == b.address: b.skip = "addr"
  190. elif sort == "txid" and a.txid == b.txid: b.skip = "txid"
  191. for i in unsp:
  192. amt = str(trim_exponent(i.amount))
  193. lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
  194. i.amt = " "*lfill + amt
  195. i.days = int(i.confirmations * g.mins_per_block / (60*24))
  196. i.mmid,i.label = parse_mmgen_label(i.account)
  197. if i.skip == "addr":
  198. i.addr = "|" + "." * 33
  199. else:
  200. if show_mmaddr and i.mmid:
  201. acct_w = min(max_acct_len, max(24,int(addr_w-10)))
  202. btaddr_w = addr_w - acct_w - 1
  203. dots = ".." if btaddr_w < len(i.address) else ""
  204. i.addr = "%s%s %s" % (
  205. i.address[:btaddr_w-len(dots)],
  206. dots,
  207. i.account[:acct_w])
  208. else:
  209. i.addr = i.address
  210. dots = "..." if tx_w < 64 else ""
  211. i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
  212. else i.txid[:tx_w-len(dots)]+dots
  213. sort_info = ["reverse"] if reverse else []
  214. sort_info.append(sort if sort else "unsorted")
  215. if group and (sort == "address" or sort == "txid"):
  216. sort_info.append("grouped")
  217. out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
  218. for n,i in enumerate(unsp):
  219. d = i.days if show_days else i.confirmations
  220. out.append(fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,d))
  221. msg("\n".join(out) +"\n\n" + print_to_file_msg + options_msg)
  222. print_to_file_msg = ""
  223. immed_chars = "atDdAMrgmeqpvw"
  224. skip_prompt = False
  225. while True:
  226. reply = get_char(prompt, immed_chars=immed_chars)
  227. if reply == 'a': unspent.sort(key=s_amt); sort = "amount"
  228. elif reply == 't': unspent.sort(key=s_txid); sort = "txid"
  229. elif reply == 'D': show_days = False if show_days else True
  230. elif reply == 'd': unspent.sort(key=s_addr); sort = "address"
  231. elif reply == 'A': unspent.sort(key=s_age); sort = "age"
  232. elif reply == 'M':
  233. unspent.sort(key=s_mmgen); sort = "mmgen"
  234. show_mmaddr = True
  235. elif reply == 'r':
  236. unspent.reverse()
  237. reverse = False if reverse else True
  238. elif reply == 'g': group = False if group else True
  239. elif reply == 'm': show_mmaddr = False if show_mmaddr else True
  240. elif reply == 'e': pass
  241. elif reply == 'q': pass
  242. elif reply == 'p':
  243. data = format_unspent_outputs_for_printing(unsp,sort_info,total)
  244. outfile = "listunspent[%s].out" % ",".join(sort_info)
  245. write_to_file(outfile, data)
  246. print_to_file_msg = "Data written to '%s'\n\n" % outfile
  247. elif reply == 'v':
  248. do_pager("\n".join(out))
  249. continue
  250. elif reply == 'w':
  251. data = format_unspent_outputs_for_printing(unsp,sort_info,total)
  252. do_pager(data)
  253. continue
  254. else:
  255. msg("\nInvalid input")
  256. continue
  257. break
  258. msg("\n")
  259. if reply == 'q': break
  260. return tuple(unspent)
  261. def parse_mmgen_label(s,check_label_len=False):
  262. if not s: return "",""
  263. try: w1,w2 = s.split(None,1)
  264. except: w1,w2 = s,""
  265. if not is_mmgen_addr(w1): return "",w1
  266. if check_label_len: check_addr_label(w2)
  267. return w1,w2
  268. def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False):
  269. td = c.decoderawtransaction(tx_hex)
  270. out = "TRANSACTION DATA\n\n"
  271. if metadata:
  272. out += "Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n".format(*metadata)
  273. out += "Inputs:\n\n"
  274. total_in = 0
  275. for n,i in enumerate(td['vin']):
  276. for j in inputs_data:
  277. if j['txid'] == i['txid'] and j['vout'] == i['vout']:
  278. days = int(j['confirmations'] * g.mins_per_block / (60*24))
  279. total_in += j['amount']
  280. addr = j['address']
  281. if j['account']:
  282. tmp = j['account'].split(None,1)
  283. mmid,label = tmp if len(tmp) == 2 else (tmp[0],"")
  284. label = label or ""
  285. else:
  286. mmid,label = "",""
  287. mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
  288. for d in (
  289. (n+1, "tx,vout:", "%s,%s" % (i['txid'], i['vout'])),
  290. ("", "address:", addr + mmid_str),
  291. ("", "label:", label),
  292. ("", "amount:", "%s BTC" % trim_exponent(j['amount'])),
  293. ("", "confirmations:", "%s (around %s days)" % (j['confirmations'], days))
  294. ):
  295. if d[2]: out += ("%3s %-8s %s\n" % d)
  296. out += "\n"
  297. break
  298. total_out = 0
  299. out += "Outputs:\n\n"
  300. for n,i in enumerate(td['vout']):
  301. addr = i['scriptPubKey']['addresses'][0]
  302. mmid,label = b2m_map[addr] if addr in b2m_map else ("","")
  303. mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
  304. total_out += i['value']
  305. for d in (
  306. (n+1, "address:", addr + mmid_str),
  307. ("", "label:", label),
  308. ("", "amount:", trim_exponent(i['value']))
  309. ):
  310. if d[2]: out += ("%3s %-8s %s\n" % d)
  311. out += "\n"
  312. out += "Total input: %s BTC\n" % trim_exponent(total_in)
  313. out += "Total output: %s BTC\n" % trim_exponent(total_out)
  314. out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out)
  315. if pager: do_pager(out)
  316. else: msg("\n"+out)
  317. def parse_tx_data(tx_data,infile):
  318. if len(tx_data) != 4:
  319. msg("'%s': not a transaction file" % infile)
  320. sys.exit(2)
  321. err_fmt = "Transaction %s is invalid"
  322. if len(tx_data[0].split()) != 3:
  323. msg(err_fmt % "metadata")
  324. sys.exit(2)
  325. try: unhexlify(tx_data[1])
  326. except:
  327. msg(err_fmt % "hex data")
  328. sys.exit(2)
  329. else:
  330. if not tx_data:
  331. msg("Transaction is empty!")
  332. sys.exit(2)
  333. try:
  334. inputs_data = eval(tx_data[2])
  335. except:
  336. msg(err_fmt % "inputs data")
  337. sys.exit(2)
  338. else:
  339. if not inputs_data:
  340. msg("Transaction has no inputs!")
  341. sys.exit(2)
  342. try:
  343. map_data = eval(tx_data[3])
  344. except:
  345. msg(err_fmt % "mmgen to btc address map data")
  346. sys.exit(2)
  347. return tx_data[0].split(),tx_data[1],inputs_data,map_data
  348. def select_outputs(unspent,prompt):
  349. while True:
  350. reply = my_raw_input(prompt,allowed_chars="0123456789 -").strip()
  351. if not reply: continue
  352. from mmgen.util import parse_address_list
  353. selected = parse_address_list(reply,sep=None)
  354. if not selected: continue
  355. if selected[-1] > len(unspent):
  356. msg("Inputs must be less than %s" % len(unspent))
  357. continue
  358. return selected
  359. def is_mmgen_seed(s):
  360. import re
  361. return len(s) == 8 and re.match(r"^[0123456789ABCDEF]*$",s)
  362. def is_mmgen_num(s):
  363. import re
  364. return len(s) <= g.mmgen_idx_max_digits \
  365. and re.match(r"^[123456789]+[0123456789]*$",s)
  366. def is_mmgen_addr(s):
  367. import re
  368. return len(s) > 9 and s[8] == ':' \
  369. and re.match(r"^[0123456789ABCDEF]*$",s[:8]) \
  370. and len(s[9:]) <= g.mmgen_idx_max_digits \
  371. and re.match(r"^[123456789]+[0123456789]*$",s[9:])
  372. def is_btc_addr(s):
  373. from mmgen.bitcoin import verify_addr
  374. return verify_addr(s)
  375. def btc_addr_to_mmgen_addr(btc_addr,b2m_map):
  376. if btc_addr in b2m_map:
  377. return b2m_map[btc_addr]
  378. return "",""
  379. def mmgen_addr_to_walletd(c,mmaddr,acct_data):
  380. # We don't want to create a new object, so we'll use append()
  381. if not acct_data:
  382. for i in c.listaccounts():
  383. acct_data.append(i)
  384. for a in acct_data:
  385. if not a: continue
  386. try:
  387. w1,w2 = a.split(None,1)
  388. except:
  389. w1,w2 = a,""
  390. if w1 == mmaddr:
  391. acct = a
  392. break
  393. else:
  394. return "",""
  395. alist = c.getaddressesbyaccount(acct)
  396. if len(alist) != 1:
  397. msg("""
  398. ERROR: More than one address found for account: "%s".
  399. The tracking "wallet.dat" file appears to have been altered by a non-%s
  400. program. Please restore "wallet.dat" from a backup or create a new wallet
  401. and re-import your addresses.
  402. """.strip() % (acct,g.proj_name_cap))
  403. sys.exit(3)
  404. return alist[0],w2
  405. def mmgen_addr_to_addr_data(m,addr_data):
  406. no_data_msg = """
  407. No data found for MMgen address '%s'. Please import this address into
  408. your tracking wallet, or supply an address file for it on the command line.
  409. """.strip() % m
  410. warn_msg = """
  411. Warning: no data for address '%s' exists in the wallet, so it was
  412. taken from the user-supplied address file. You're strongly advised to
  413. import this address into your tracking wallet before proceeding with
  414. this transaction. The address will not be tracked until you do so.
  415. """.strip() % m
  416. fail_msg = """
  417. No data found for MMgen address '%s' in either wallet or supplied
  418. address file. Please import this address into your tracking wallet, or
  419. supply an address file for it on the command line.
  420. """.strip() % m
  421. ID,num = m.split(":")
  422. from binascii import unhexlify
  423. try: unhexlify(ID)
  424. except: pass
  425. else:
  426. try: num = int(num)
  427. except: pass
  428. else:
  429. if not addr_data:
  430. msg(no_data_msg)
  431. sys.exit(2)
  432. for i in addr_data:
  433. if ID == i[0]:
  434. for j in i[1]:
  435. if j[0] == num:
  436. msg(warn_msg)
  437. if not user_confirm("Continue anyway?"):
  438. sys.exit(1)
  439. return j[1],(j[2] if len(j) == 3 else "")
  440. msg(fail_msg)
  441. sys.exit(2)
  442. msg("Invalid format: %s" % m)
  443. sys.exit(3)
  444. def check_mmgen_to_btc_addr_mappings(inputs_data,b2m_map,infiles,seeds,opts):
  445. in_maplist = [(i['account'].split()[0],i['address'])
  446. for i in inputs_data if i['account']
  447. and is_mmgen_addr(i['account'].split()[0])]
  448. out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
  449. for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
  450. if not maplist: continue
  451. qmsg("Checking MMGen -> BTC address mappings for %s" % label)
  452. mmaddrs = [i[0] for i in maplist]
  453. from copy import deepcopy
  454. pairs = get_keys_for_mmgen_addrs(mmaddrs,
  455. deepcopy(infiles),seeds,opts,gen_pairs=True)
  456. for a,b in zip(sorted(pairs),sorted(maplist)):
  457. if a != b:
  458. msg("""
  459. MMGen -> BTC address mappings differ!
  460. In transaction: %s
  461. Generated from seed: %s
  462. """.strip() % (" ".join(a)," ".join(b)))
  463. sys.exit(3)
  464. qmsg("Address mappings OK")
  465. def check_addr_label(label):
  466. if len(label) > g.max_addr_label_len:
  467. msg("'%s': overlong label (length must be <=%s)" %
  468. (label,g.max_addr_label_len))
  469. sys.exit(3)
  470. for ch in label:
  471. if ch not in g.addr_label_symbols:
  472. msg("""
  473. "%s": illegal character in label "%s".
  474. Only ASCII printable characters are permitted.
  475. """.strip() % (ch,label))
  476. sys.exit(3)
  477. def parse_addrs_file(f):
  478. lines = get_lines_from_file(f,"address data",remove_comments=True)
  479. try:
  480. seed_id,obrace = lines[0].split()
  481. except:
  482. msg("Invalid first line: '%s'" % lines[0])
  483. sys.exit(3)
  484. cbrace = lines[-1]
  485. if obrace != '{':
  486. msg("'%s': invalid first line" % lines[0])
  487. elif cbrace != '}':
  488. msg("'%s': invalid last line" % cbrace)
  489. elif not is_mmgen_seed(seed_id):
  490. msg("'%s': invalid Seed ID" % seed_id)
  491. else:
  492. ret = []
  493. for i in lines[1:-1]:
  494. d = i.split(None,2)
  495. if not is_mmgen_num(d[0]):
  496. msg("'%s': invalid address num. in line: %s" % (d[0],d))
  497. sys.exit(3)
  498. if not is_btc_addr(d[1]):
  499. msg("'%s': invalid Bitcoin address" % d[1])
  500. sys.exit(3)
  501. if len(d) == 3:
  502. check_addr_label(d[2])
  503. ret.append(tuple(d))
  504. return seed_id,ret
  505. sys.exit(3)
  506. def sign_transaction(c,tx_hex,sig_data,keys=None):
  507. if keys:
  508. qmsg("%s keys total" % len(keys))
  509. if g.debug: print "Keys:\n %s" % "\n ".join(keys)
  510. msg_r("Signing transaction...")
  511. from mmgen.rpc import exceptions
  512. try:
  513. sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
  514. except exceptions.InvalidAddressOrKey:
  515. msg("failed\nInvalid address or key")
  516. sys.exit(3)
  517. return sig_tx
  518. def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,seeds,opts,gen_pairs=False):
  519. seed_ids = list(set([i[:8] for i in mmgen_addrs]))
  520. seed_ids_save = seed_ids[0:] # deep copy
  521. ret = []
  522. seeds_keys = [i for i in seed_ids if i in seeds]
  523. while seed_ids:
  524. if seeds_keys:
  525. seed = seeds[seeds_keys.pop(0)]
  526. else:
  527. infile = False
  528. if infiles:
  529. infile = infiles.pop(0)
  530. seed = get_seed_retry(infile,opts)
  531. elif "from_brain" in opts or "from_mnemonic" in opts \
  532. or "from_seed" in opts or "from_incog" in opts:
  533. msg("Need data for seed ID %s" % seed_ids[0])
  534. seed = get_seed_retry("",opts)
  535. else:
  536. b,p,v = ("A seed","","is") if len(seed_ids) == 1 \
  537. else ("Seed","s","are")
  538. msg("ERROR: %s source%s %s required for the following seed ID%s: %s"%
  539. (b,p,v,p," ".join(seed_ids)))
  540. sys.exit(2)
  541. seed_id = make_chksum_8(seed)
  542. if seed_id in seed_ids:
  543. seed_ids.remove(seed_id)
  544. addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
  545. seeds[seed_id] = seed
  546. from mmgen.addr import generate_keys,generate_addrs
  547. if gen_pairs:
  548. o = {"gen_what":"addresses"}
  549. ret += [("%s:%s" % (seed_id,i['num']),i['addr'])
  550. for i in generate_addrs(seed, addr_ids, o)]
  551. else:
  552. ret += [i['wif'] for i in generate_keys(seed, addr_ids)]
  553. else:
  554. if seed_id in seed_ids_save:
  555. msg_r("Ignoring duplicate seed source")
  556. if infile: msg(" '%s'" % infile)
  557. else: msg(" for ID %s" % seed_id)
  558. else:
  559. msg("Seed source produced an invalid seed ID (%s)" % seed_id)
  560. if "from_incog" in opts or infile.split(".")[-1] == g.incog_ext:
  561. msg(
  562. """Incorrect hash preset, password or incognito wallet data
  563. Trying again...""")
  564. infiles.insert(0,infile) # ugly!
  565. elif infile:
  566. msg("Invalid input file '%s'" % infile)
  567. sys.exit(2)
  568. return ret
  569. def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
  570. try:
  571. sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
  572. except:
  573. from mmgen.rpc import exceptions
  574. msg("Using keys in wallet.dat as per user request")
  575. prompt = "Enter passphrase for bitcoind wallet: "
  576. while True:
  577. passwd = get_bitcoind_passphrase(prompt,opts)
  578. try:
  579. c.walletpassphrase(passwd, 9999)
  580. except exceptions.WalletPassphraseIncorrect:
  581. msg("Passphrase incorrect")
  582. else:
  583. msg("Passphrase OK"); break
  584. sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
  585. msg("Locking wallet")
  586. try:
  587. c.walletlock()
  588. except:
  589. msg("Failed to lock wallet")
  590. return sig_tx
  591. def preverify_keys(addrs_orig, keys_orig):
  592. addrs,keys,wrong_keys = set(addrs_orig[0:]),set(keys_orig[0:]),[]
  593. if len(keys) < len(addrs):
  594. msg("ERROR: not enough keys (%s) for number of non-%s addresses (%s)" %
  595. (len(keys),g.proj_name_cap,len(addrs)))
  596. sys.exit(2)
  597. import mmgen.bitcoin as b
  598. qmsg_r('Checking that user-supplied key list contains valid keys...')
  599. invalid_keys = []
  600. for n,k in enumerate(keys,1):
  601. c = False if k[0] == '5' else True
  602. if b.wiftohex(k,compressed=c) == False:
  603. invalid_keys.append(k)
  604. if invalid_keys:
  605. s = "" if len(invalid_keys) == 1 else "s"
  606. msg("\n%s/%s invalid key%s in keylist!\n" % (len(invalid_keys),len(keys),s))
  607. sys.exit(2)
  608. else: qmsg("OK")
  609. msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
  610. try:
  611. for n,k in enumerate(keys,1):
  612. msg_r("\rkey %s of %s" % (n,len(keys)))
  613. c = False if k[0] == '5' else True
  614. hexkey = b.wiftohex(k,compressed=c)
  615. addr = b.privnum2addr(int(hexkey,16),compressed=c)
  616. if addr in addrs:
  617. addrs.remove(addr)
  618. if not addrs: break
  619. else:
  620. wrong_keys.append(k)
  621. except KeyboardInterrupt:
  622. msg("\nSkipping")
  623. else:
  624. msg("")
  625. if wrong_keys:
  626. s = "" if len(wrong_keys) == 1 else "s"
  627. msg("%s extra key%s found" % (len(wrong_keys),s))
  628. if addrs:
  629. s = "" if len(addrs) == 1 else "es"
  630. msg("No keys found for the following non-%s address%s:" %
  631. (g.proj_name_cap,s))
  632. print " %s" % "\n ".join(addrs)
  633. sys.exit(2)
  634. def missing_keys_errormsg(other_addrs):
  635. msg("""
  636. A key file must be supplied (or use the "-w" option) for the following
  637. non-mmgen address%s:
  638. """.strip() % ("" if len(other_addrs) == 1 else "es"))
  639. print " %s" % "\n ".join([i['address'] for i in other_addrs])