tx.py 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  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. import sys, os
  22. from binascii import unhexlify
  23. from decimal import Decimal
  24. import mmgen.config as g
  25. from mmgen.util import *
  26. from mmgen.crypto import get_seed_retry
  27. from mmgen.term import do_pager,get_char
  28. txmsg = {
  29. 'not_enough_btc': "Not enough BTC in the inputs for this transaction (%s BTC)",
  30. 'throwaway_change': """
  31. ERROR: This transaction produces change (%s BTC); however, no change
  32. address was specified.
  33. """.strip(),
  34. 'mixed_inputs': """
  35. NOTE: This transaction uses a mixture of both mmgen and non-mmgen inputs,
  36. which makes the signing process more complicated. When signing the
  37. transaction, keys for the non-mmgen inputs must be supplied in a separate
  38. file using the '-k' option to {}-txsign.
  39. Selected mmgen inputs: %s""".format(g.proj_name.lower()),
  40. 'too_many_acct_addresses': """
  41. ERROR: More than one address found for account: "%s".
  42. The tracking "wallet.dat" file appears to have been altered by a non-{g.proj_name}
  43. program. Please restore "wallet.dat" from a backup or create a new wallet
  44. and re-import your addresses.""".strip().format(g=g),
  45. 'addrfile_no_data_msg': """
  46. No data found for MMgen address '%s'. Please import this address into
  47. your tracking wallet, or supply an address file for it on the command line.
  48. """.strip(),
  49. 'addrfile_warn_msg': """
  50. Warning: no data for address '{mmaddr}' was found in the tracking wallet, so
  51. this information was taken from the user-supplied address file. You're strongly
  52. advised to import this address into your tracking wallet before proceeding with
  53. this transaction. The address will not be tracked until you do so.
  54. """.strip(),
  55. 'addrfile_fail_msg': """
  56. No data for MMgen address '{mmaddr}' could be found in either the tracking
  57. wallet or the supplied address file. Please import this address into your
  58. tracking wallet, or supply an address file for it on the command line.
  59. """.strip(),
  60. 'no_spendable_outputs': """
  61. No spendable outputs found! Import addresses with balances into your
  62. watch-only wallet using '{}-addrimport' and then re-run this program.
  63. """.strip().format(g.proj_name.lower()),
  64. 'mapping_error': """
  65. MMGen -> BTC address mappings differ!
  66. In transaction: %s
  67. Generated from seed: %s
  68. """.strip(),
  69. 'skip_mapping_checks_warning': """
  70. You've chosen the '--all-keys-from-file' option. Since all signing keys will
  71. be taken from this file, no {pnm} seed source will be consulted and {pnm}-to-
  72. BTC mapping checks cannot not be performed. Were an attacker to compromise
  73. your tracking wallet or raw transaction file, he could thus cause you to spend
  74. coin to an unintended address. For greater security, supply a trusted {pnm}
  75. address file for your output addresses on the command line.
  76. """.strip().format(pnm=g.proj_name),
  77. 'missing_mappings': """
  78. No information was found in the supplied address files for the following {pnm}
  79. addresses: %s
  80. The {pnm}-to-BTC mappings for these addresses cannot be verified!
  81. """.strip().format(pnm=g.proj_name),
  82. }
  83. def connect_to_bitcoind():
  84. host,port,user,passwd = "localhost",8332,"rpcuser","rpcpassword"
  85. cfg = get_bitcoind_cfg_options((user,passwd))
  86. import mmgen.rpc.connection
  87. f = mmgen.rpc.connection.BitcoinConnection
  88. try:
  89. c = f(cfg[user],cfg[passwd],host,port)
  90. except:
  91. msg("Unable to establish RPC connection with bitcoind")
  92. sys.exit(2)
  93. return c
  94. def trim_exponent(n):
  95. '''Remove exponent and trailing zeros.
  96. '''
  97. d = Decimal(n)
  98. return d.quantize(Decimal(1)) if d == d.to_integral() else d.normalize()
  99. def is_btc_amt(amt):
  100. # amt must be a string!
  101. from decimal import Decimal
  102. try:
  103. ret = Decimal(amt)
  104. except:
  105. msg("%s: Invalid amount" % amt)
  106. return False
  107. if g.debug:
  108. print "Decimal(amt): %s\nAs tuple: %s" % (amt,repr(ret.as_tuple()))
  109. if ret.as_tuple()[-1] < -8:
  110. msg("%s: Too many decimal places in amount" % amt)
  111. return False
  112. if ret == 0:
  113. msg("Requested zero BTC amount")
  114. return False
  115. return trim_exponent(ret)
  116. def normalize_btc_amt(amt):
  117. ret = is_btc_amt(amt)
  118. if ret: return ret
  119. else: sys.exit(3)
  120. def get_bitcoind_cfg_options(cfg_keys):
  121. if "HOME" in os.environ: # Linux
  122. homedir,datadir = os.environ["HOME"],".bitcoin"
  123. elif "HOMEPATH" in os.environ: # Windows:
  124. homedir,data_dir = os.environ["HOMEPATH"],r"Application Data\Bitcoin"
  125. else:
  126. msg("Neither $HOME nor %HOMEPATH% are set")
  127. msg("Don't know where to look for 'bitcoin.conf'")
  128. sys.exit(3)
  129. cfg_file = os.sep.join((homedir, datadir, "bitcoin.conf"))
  130. cfg = dict([(k,v) for k,v in [split2(line.translate(None,"\t "),"=")
  131. for line in get_lines_from_file(cfg_file)] if k in cfg_keys])
  132. for k in set(cfg_keys) - set(cfg.keys()):
  133. msg("Configuration option '%s' must be set in %s" % (k,cfg_file))
  134. sys.exit(2)
  135. return cfg
  136. def format_unspent_outputs_for_printing(out,sort_info,total):
  137. pfs = " %-4s %-67s %-34s %-12s %-13s %-8s %-10s %s"
  138. pout = [pfs % ("Num","TX id,Vout","Address","MMgen ID",
  139. "Amount (BTC)","Conf.","Age (days)", "Comment")]
  140. for n,i in enumerate(out):
  141. addr = "=" if i.skip == "addr" and "grouped" in sort_info else i.address
  142. tx = " " * 63 + "=" \
  143. if i.skip == "txid" and "grouped" in sort_info else str(i.txid)
  144. s = pfs % (str(n+1)+")", tx+","+str(i.vout),addr,
  145. i.mmid,i.amt,i.confirmations,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,opts):
  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):
  157. m = parse_mmgen_label(i.account)[0]
  158. if m: return "{}:{:>0{w}}".format(w=g.mmgen_idx_max_digits, *m.split(":"))
  159. else: return "G" + i.account
  160. sort,group,show_days,show_mmaddr,reverse = "age",False,False,True,True
  161. unspent.sort(key=s_age,reverse=reverse) # Reverse age sort by default
  162. total = trim_exponent(sum([i.amount for i in unspent]))
  163. max_acct_len = max([len(i.account) for i in unspent])
  164. hdr_fmt = "UNSPENT OUTPUTS (sort order: %s) Total BTC: %s"
  165. options_msg = """
  166. Sort options: [t]xid, [a]mount, a[d]dress, [A]ge, [r]everse, [M]mgen addr
  167. Display options: show [D]ays, [g]roup, show [m]mgen addr, r[e]draw screen
  168. """.strip()
  169. prompt = \
  170. "('q' = quit sorting, 'p' = print to file, 'v' = pager view, 'w' = wide view): "
  171. from copy import deepcopy
  172. from mmgen.term import get_terminal_size
  173. write_to_file_msg = ""
  174. msg("")
  175. while True:
  176. cols = get_terminal_size()[0]
  177. if cols < g.min_screen_width:
  178. msg("%s-txcreate requires a screen at least %s characters wide" %
  179. (g.proj_name.lower(),g.min_screen_width))
  180. sys.exit(2)
  181. addr_w = min(34+((1+max_acct_len) if show_mmaddr else 0),cols-46)
  182. acct_w = min(max_acct_len, max(24,int(addr_w-10)))
  183. btaddr_w = addr_w - acct_w - 1
  184. tx_w = max(11,min(64, cols-addr_w-32))
  185. txdots = "..." if tx_w < 64 else ""
  186. fs = " %-4s %-" + str(tx_w) + "s %-2s %-" + str(addr_w) + "s %-13s %-s"
  187. table_hdr = fs % ("Num","TX id Vout","","Address","Amount (BTC)",
  188. "Age(d)" if show_days else "Conf.")
  189. unsp = deepcopy(unspent)
  190. for i in unsp: i.skip = ""
  191. if group and (sort == "address" or sort == "txid"):
  192. for a,b in [(unsp[i],unsp[i+1]) for i in range(len(unsp)-1)]:
  193. if sort == "address" and a.address == b.address: b.skip = "addr"
  194. elif sort == "txid" and a.txid == b.txid: b.skip = "txid"
  195. for i in unsp:
  196. amt = str(trim_exponent(i.amount))
  197. lfill = 3 - len(amt.split(".")[0]) if "." in amt else 3 - len(amt)
  198. i.amt = " "*lfill + amt
  199. i.days = int(i.confirmations * g.mins_per_block / (60*24))
  200. i.age = i.days if show_days else i.confirmations
  201. i.mmid,i.label = parse_mmgen_label(i.account)
  202. if i.skip == "addr":
  203. i.addr = "|" + "." * 33
  204. else:
  205. if show_mmaddr:
  206. dots = ".." if btaddr_w < len(i.address) else ""
  207. i.addr = "%s%s %s" % (
  208. i.address[:btaddr_w-len(dots)],
  209. dots,
  210. i.account[:acct_w])
  211. else:
  212. i.addr = i.address
  213. i.tx = " " * (tx_w-4) + "|..." if i.skip == "txid" \
  214. else i.txid[:tx_w-len(txdots)]+txdots
  215. sort_info = ["reverse"] if reverse else []
  216. sort_info.append(sort if sort else "unsorted")
  217. if group and (sort == "address" or sort == "txid"):
  218. sort_info.append("grouped")
  219. out = [hdr_fmt % (" ".join(sort_info), total), table_hdr]
  220. out += [fs % (str(n+1)+")",i.tx,i.vout,i.addr,i.amt,i.age)
  221. for n,i in enumerate(unsp)]
  222. msg("\n".join(out) +"\n\n" + write_to_file_msg + options_msg)
  223. write_to_file_msg = ""
  224. skip_prompt = False
  225. while True:
  226. reply = get_char(prompt, immed_chars="atDdAMrgmeqpvw")
  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. d = format_unspent_outputs_for_printing(unsp,sort_info,total)
  244. of = "listunspent[%s].out" % ",".join(sort_info)
  245. write_to_file(of, d, opts,"",False,False)
  246. write_to_file_msg = "Data written to '%s'\n\n" % of
  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. l = split2(s)
  263. if not is_mmgen_addr(l[0]): return "",s
  264. if check_label_len: check_addr_label(l[1])
  265. return tuple(l)
  266. def view_tx_data(c,inputs_data,tx_hex,b2m_map,metadata=[],pager=False):
  267. td = c.decoderawtransaction(tx_hex)
  268. out = "TRANSACTION DATA\n\n"
  269. if metadata:
  270. out += "Header: [Tx ID: {}] [Amount: {} BTC] [Time: {}]\n\n".format(*metadata)
  271. out += "Inputs:\n\n"
  272. total_in = 0
  273. for n,i in enumerate(td['vin']):
  274. for j in inputs_data:
  275. if j['txid'] == i['txid'] and j['vout'] == i['vout']:
  276. days = int(j['confirmations'] * g.mins_per_block / (60*24))
  277. total_in += j['amount']
  278. addr = j['address']
  279. mmid,label = parse_mmgen_label(j['account']) \
  280. if 'account' in j else ("","")
  281. mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
  282. for d in (
  283. (n+1, "tx,vout:", "%s,%s" % (i['txid'], i['vout'])),
  284. ("", "address:", addr + mmid_str),
  285. ("", "label:", label),
  286. ("", "amount:", "%s BTC" % trim_exponent(j['amount'])),
  287. ("", "confirmations:", "%s (around %s days)" % (j['confirmations'], days))
  288. ):
  289. if d[2]: out += ("%3s %-8s %s\n" % d)
  290. out += "\n"
  291. break
  292. total_out = 0
  293. out += "Outputs:\n\n"
  294. for n,i in enumerate(td['vout']):
  295. addr = i['scriptPubKey']['addresses'][0]
  296. mmid,label = b2m_map[addr] if addr in b2m_map else ("","")
  297. mmid_str = ((34-len(addr))*" " + " (%s)" % mmid) if mmid else ""
  298. total_out += i['value']
  299. for d in (
  300. (n+1, "address:", addr + mmid_str),
  301. ("", "label:", label),
  302. ("", "amount:", trim_exponent(i['value']))
  303. ):
  304. if d[2]: out += ("%3s %-8s %s\n" % d)
  305. out += "\n"
  306. out += "Total input: %s BTC\n" % trim_exponent(total_in)
  307. out += "Total output: %s BTC\n" % trim_exponent(total_out)
  308. out += "TX fee: %s BTC\n" % trim_exponent(total_in-total_out)
  309. if pager: do_pager(out)
  310. else: print "\n"+out
  311. def parse_tx_data(tx_data,infile):
  312. try:
  313. metadata,tx_hex,inputs_data,outputs_data = tx_data
  314. except:
  315. msg("'%s': not a transaction file" % infile)
  316. sys.exit(2)
  317. err_fmt = "Transaction %s is invalid"
  318. if len(metadata.split()) != 3:
  319. msg(err_fmt % "metadata")
  320. sys.exit(2)
  321. try: unhexlify(tx_hex)
  322. except:
  323. msg(err_fmt % "hex data")
  324. sys.exit(2)
  325. else:
  326. if not tx_hex:
  327. msg("Transaction is empty!")
  328. sys.exit(2)
  329. try:
  330. inputs_data = eval(inputs_data)
  331. except:
  332. msg(err_fmt % "inputs data")
  333. sys.exit(2)
  334. try:
  335. outputs_data = eval(outputs_data)
  336. except:
  337. msg(err_fmt % "mmgen to btc address map data")
  338. sys.exit(2)
  339. return metadata.split(),tx_hex,inputs_data,outputs_data
  340. def select_outputs(unspent,prompt):
  341. while True:
  342. reply = my_raw_input(prompt).strip()
  343. if not reply: continue
  344. from mmgen.util import parse_address_list
  345. selected = parse_address_list(reply,sep=None)
  346. if not selected: continue
  347. if selected[-1] > len(unspent):
  348. msg("Inputs must be less than %s" % len(unspent))
  349. continue
  350. return selected
  351. def is_mmgen_seed_id(s):
  352. import re
  353. return True if re.match(r"^[0123456789ABCDEF]{8}$",s) else False
  354. def is_mmgen_idx(s):
  355. import re
  356. m = g.mmgen_idx_max_digits
  357. return True if re.match(r"^[0123456789]{1,"+str(m)+r"}$",s) else False
  358. def is_mmgen_addr(s):
  359. seed_id,idx = split2(s,":")
  360. return is_mmgen_seed_id(seed_id) and is_mmgen_idx(idx)
  361. def is_btc_addr(s):
  362. from mmgen.bitcoin import verify_addr
  363. return verify_addr(s)
  364. def mmaddr2btcaddr_bitcoind(c,mmaddr,acct_data):
  365. # We don't want to create a new object, so we'll use append()
  366. if not acct_data:
  367. for i in c.listaccounts():
  368. acct_data.append(i)
  369. for acct in acct_data:
  370. m,comment = parse_mmgen_label(acct)
  371. if m == mmaddr:
  372. addrlist = c.getaddressesbyaccount(acct)
  373. if len(addrlist) == 1:
  374. return addrlist[0],comment
  375. else:
  376. msg(txmsg['too_many_acct_addresses'] % acct); sys.exit(2)
  377. return "",""
  378. def mmaddr2btcaddr_addrfile(mmaddr,addr_data,silent=False):
  379. mmid,mmidx = mmaddr.split(":")
  380. for ad in addr_data:
  381. if mmid == ad[0]:
  382. for j in ad[1]:
  383. if j[0] == mmidx:
  384. if not silent:
  385. msg(txmsg['addrfile_warn_msg'].format(mmaddr=mmaddr))
  386. if not user_confirm("Continue anyway?"):
  387. sys.exit(1)
  388. return j[1:] if len(j) == 3 else (j[1],"")
  389. if silent: return "",""
  390. else: msg(txmsg['addrfile_fail_msg'].format(mmaddr=mmaddr)); sys.exit(2)
  391. def check_mmgen_to_btc_addr_mappings(mmgen_inputs,b2m_map,infiles,saved_seeds,opts):
  392. in_maplist = [(i['account'].split()[0],i['address']) for i in mmgen_inputs]
  393. out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
  394. for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
  395. if not maplist: continue
  396. qmsg("Checking MMGen -> BTC address mappings for %s" % label)
  397. pairs = get_keys_for_mmgen_addrs([i[0] for i in maplist],
  398. infiles,saved_seeds,opts,gen_pairs=True)
  399. for a,b in zip(sorted(pairs),sorted(maplist)):
  400. if a != b:
  401. msg(txmsg['mapping_error'] % (" ".join(a)," ".join(b)))
  402. sys.exit(3)
  403. qmsg("Address mappings OK")
  404. def check_addr_label(label):
  405. if len(label) > g.max_addr_label_len:
  406. msg("'%s': overlong label (length must be <=%s)" %
  407. (label,g.max_addr_label_len))
  408. sys.exit(3)
  409. for ch in label:
  410. if ch not in g.addr_label_symbols:
  411. msg("""
  412. "%s": illegal character in label "%s".
  413. Only ASCII printable characters are permitted.
  414. """.strip() % (ch,label))
  415. sys.exit(3)
  416. def make_addr_data_chksum(addr_data):
  417. nchars = 24
  418. return make_chksum_N(
  419. " ".join(["{} {}".format(*d[:2]) for d in addr_data]), nchars, sep=True
  420. )
  421. def check_addr_data_hash(seed_id,addr_data):
  422. def s_addrdata(a): return int(a[0])
  423. addr_data_chksum = make_addr_data_chksum(sorted(addr_data,key=s_addrdata))
  424. from mmgen.addr import fmt_addr_idxs
  425. fl = fmt_addr_idxs([int(a[0]) for a in addr_data])
  426. msg("Computed checksum for addr data {}[{}]: {}".format(
  427. seed_id,fl,addr_data_chksum))
  428. qmsg("Check this value against your records")
  429. def parse_addrs_file(f):
  430. lines = get_lines_from_file(f,"address data",trim_comments=True)
  431. try:
  432. seed_id,obrace = lines[0].split()
  433. except:
  434. msg("Invalid first line: '%s'" % lines[0])
  435. sys.exit(3)
  436. cbrace = lines[-1]
  437. if obrace != '{':
  438. msg("'%s': invalid first line" % lines[0])
  439. elif cbrace != '}':
  440. msg("'%s': invalid last line" % cbrace)
  441. elif not is_mmgen_seed_id(seed_id):
  442. msg("'%s': invalid Seed ID" % seed_id)
  443. else:
  444. addr_data = []
  445. for i in lines[1:-1]:
  446. d = i.split(None,2)
  447. if not is_mmgen_idx(d[0]):
  448. msg("'%s': invalid address num. in line: %s" % (d[0],d))
  449. sys.exit(3)
  450. if not is_btc_addr(d[1]):
  451. msg("'%s': invalid Bitcoin address" % d[1])
  452. sys.exit(3)
  453. if len(d) == 3:
  454. check_addr_label(d[2])
  455. addr_data.append(tuple(d))
  456. check_addr_data_hash(seed_id,addr_data)
  457. return seed_id,addr_data
  458. sys.exit(3)
  459. def sign_transaction(c,tx_hex,sig_data,keys=None):
  460. if keys:
  461. qmsg("%s keys total" % len(keys))
  462. if g.debug: print "Keys:\n %s" % "\n ".join(keys)
  463. msg_r("Signing transaction...")
  464. from mmgen.rpc import exceptions
  465. try:
  466. sig_tx = c.signrawtransaction(tx_hex,sig_data,keys)
  467. except exceptions.InvalidAddressOrKey:
  468. msg("failed\nInvalid address or key")
  469. sys.exit(3)
  470. return sig_tx
  471. def get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts):
  472. if seed_id in saved_seeds.keys():
  473. return saved_seeds[seed_id]
  474. while True:
  475. if infiles:
  476. seed = get_seed_retry(infiles.pop(0),opts)
  477. elif "from_brain" in opts or "from_mnemonic" in opts \
  478. or "from_seed" in opts or "from_incog" in opts:
  479. msg("Need data for seed ID %s" % seed_id)
  480. seed = get_seed_retry("",opts)
  481. msg("User input produced seed ID %s" % make_chksum_8(seed))
  482. else:
  483. msg("ERROR: No seed source found for seed ID: %s" % seed_id)
  484. sys.exit(2)
  485. s_id = make_chksum_8(seed)
  486. saved_seeds[s_id] = seed
  487. if s_id == seed_id: return seed
  488. def get_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds,opts,gen_pairs=False):
  489. seed_ids = list(set([i[:8] for i in mmgen_addrs]))
  490. ret = []
  491. for seed_id in seed_ids:
  492. # Returns only if seed is found
  493. seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds,opts)
  494. addr_ids = [int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id]
  495. from mmgen.addr import generate_addrs
  496. if gen_pairs:
  497. ret += [("{}:{}".format(seed_id,i.num),i.addr)
  498. for i in generate_addrs(seed, addr_ids, {'gen_what':("addrs")})]
  499. else:
  500. ret += [i.wif for i in generate_addrs(
  501. seed,addr_ids,{'gen_what':("keys")})]
  502. return ret
  503. def sign_tx_with_bitcoind_wallet(c,tx_hex,sig_data,keys,opts):
  504. try:
  505. sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
  506. except:
  507. from mmgen.rpc import exceptions
  508. msg("Using keys in wallet.dat as per user request")
  509. prompt = "Enter passphrase for bitcoind wallet: "
  510. while True:
  511. passwd = get_bitcoind_passphrase(prompt,opts)
  512. try:
  513. c.walletpassphrase(passwd, 9999)
  514. except exceptions.WalletPassphraseIncorrect:
  515. msg("Passphrase incorrect")
  516. else:
  517. msg("Passphrase OK"); break
  518. sig_tx = sign_transaction(c,tx_hex,sig_data,keys)
  519. msg("Locking wallet")
  520. try:
  521. c.walletlock()
  522. except:
  523. msg("Failed to lock wallet")
  524. return sig_tx
  525. def preverify_keys(addrs_orig, keys_orig):
  526. addrs,keys,wrong_keys = set(addrs_orig[0:]),set(keys_orig[0:]),[]
  527. if len(keys) < len(addrs):
  528. msg("ERROR: not enough keys (%s) for number of non-%s addresses (%s)" %
  529. (len(keys),g.proj_name,len(addrs)))
  530. sys.exit(2)
  531. import mmgen.bitcoin as b
  532. qmsg_r('Checking that user-supplied key list contains valid keys...')
  533. invalid_keys = []
  534. for n,k in enumerate(keys,1):
  535. c = False if k[0] == '5' else True
  536. if b.wiftohex(k,compressed=c) == False:
  537. invalid_keys.append(k)
  538. if invalid_keys:
  539. s = "" if len(invalid_keys) == 1 else "s"
  540. msg("\n%s/%s invalid key%s in keylist!\n" % (len(invalid_keys),len(keys),s))
  541. sys.exit(2)
  542. else: qmsg("OK")
  543. # Check that keys match addresses:
  544. msg('Pre-verifying keys in user-supplied key list (Ctrl-C to skip)')
  545. try:
  546. for n,k in enumerate(keys,1):
  547. msg_r("\rkey %s of %s" % (n,len(keys)))
  548. c = False if k[0] == '5' else True
  549. hexkey = b.wiftohex(k,compressed=c)
  550. addr = b.privnum2addr(int(hexkey,16),compressed=c)
  551. if addr in addrs:
  552. addrs.remove(addr)
  553. if not addrs: break
  554. else:
  555. wrong_keys.append(k)
  556. except KeyboardInterrupt:
  557. msg("\nSkipping")
  558. else:
  559. msg("")
  560. if wrong_keys:
  561. s = "" if len(wrong_keys) == 1 else "s"
  562. msg("%s extra key%s found" % (len(wrong_keys),s))
  563. if addrs:
  564. s = "" if len(addrs) == 1 else "es"
  565. msg("No keys found for the following non-%s address%s:" %
  566. (g.proj_name,s))
  567. print " %s" % "\n ".join(addrs)
  568. sys.exit(2)
  569. def missing_keys_errormsg(other_addrs):
  570. msg("""
  571. A key file must be supplied (or use the "-w" option) for the following
  572. non-mmgen address%s:
  573. """.strip() % ("" if len(other_addrs) == 1 else "es"))
  574. print " %s" % "\n ".join([i['address'] for i in other_addrs])
  575. def check_mmgen_to_btc_addr_mappings_addrfile(mmgen_inputs,b2m_map,addrfiles):
  576. addr_data = [parse_addrs_file(a) for a in addrfiles]
  577. in_maplist = [(i['account'].split()[0],i['address']) for i in mmgen_inputs]
  578. out_maplist = [(i[1][0],i[0]) for i in b2m_map.items()]
  579. missing,wrong = [],[]
  580. for maplist,label in (in_maplist,"inputs"), (out_maplist,"outputs"):
  581. qmsg("Checking MMGen -> BTC address mappings for %s" % label)
  582. for i in maplist:
  583. btaddr = mmaddr2btcaddr_addrfile(i[0],addr_data,silent=True)[0]
  584. if not btaddr: missing.append(i[0])
  585. elif btaddr != i[1]: wrong.append((i[0],i[1],btaddr))
  586. if wrong:
  587. fs = " {:11} {:35} {}"
  588. msg("ERROR: The following address mappings did not match!")
  589. msg(fs.format("MMGen addr","In TX file:","In address file:"))
  590. for w in wrong: msg(fs.format(*w))
  591. sys.exit(3)
  592. if missing:
  593. confirm_or_exit(txmsg['missing_mappings'] % " ".join(missing),"continue")
  594. else: qmsg("Address mappings OK")