main_txsign.py 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/usr/bin/env python
  2. #
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2016 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. mmgen-txsign: Sign a transaction generated by 'mmgen-txcreate'
  20. """
  21. from mmgen.common import *
  22. from mmgen.seed import *
  23. from mmgen.tx import *
  24. from mmgen.addr import *
  25. pnm = g.proj_name
  26. # -w is unneeded - use bitcoin-cli walletdump instead
  27. # -w, --use-wallet-dat Get keys from a running bitcoind
  28. opts_data = {
  29. 'desc': 'Sign Bitcoin transactions generated by {pnl}-txcreate'.format(pnl=pnm.lower()),
  30. 'usage': '[opts] <transaction file>... [seed source]...',
  31. 'options': """
  32. -h, --help Print this help message.
  33. -b, --brain-params=l,p Use seed length 'l' and hash preset 'p' for brain-
  34. wallet input.
  35. -d, --outdir= d Specify an alternate directory 'd' for output.
  36. -D, --tx-id Display transaction ID and exit.
  37. -e, --echo-passphrase Print passphrase to screen when typing it.
  38. -i, --in-fmt= f Input is from wallet format 'f' (see FMT CODES below).
  39. -H, --hidden-incog-input-params=f,o Read hidden incognito data from file
  40. 'f' at offset 'o' (comma-separated).
  41. -O, --old-incog-fmt Specify old-format incognito input.
  42. -l, --seed-len= l Specify wallet seed length of 'l' bits. This option
  43. is required only for brainwallet and incognito inputs
  44. with non-standard (< {g.seed_len}-bit) seed lengths.
  45. -p, --hash-preset=p Use the scrypt hash parameters defined by preset 'p'
  46. for password hashing (default: '{g.hash_preset}').
  47. -z, --show-hash-presets Show information on available hash presets.
  48. -k, --keys-from-file=f Provide additional keys for non-{pnm} addresses
  49. -K, --key-generator=m Use method 'm' for public key generation.
  50. Options: {kgs} (default: {kg})
  51. -M, --mmgen-keys-from-file=f Provide keys for {pnm} addresses in a key-
  52. address file (output of '{pnl}-keygen'). Permits
  53. online signing without an {pnm} seed source.
  54. The key-address file is also used to verify
  55. {pnm}-to-BTC mappings, so its checksum should
  56. be recorded by the user.
  57. -P, --passwd-file= f Get {pnm} wallet or bitcoind passphrase from file 'f'
  58. -q, --quiet Suppress warnings; overwrite files without
  59. prompting
  60. -I, --info Display information about the transaction and exit.
  61. -t, --terse-info Like '--info', but produce more concise output.
  62. -v, --verbose Produce more verbose output
  63. """.format(
  64. g=g,pnm=pnm,pnl=pnm.lower(),
  65. kgs=' '.join(['{}:{}'.format(n,k) for n,k in enumerate(g.key_generators,1)]),
  66. kg=g.key_generator),
  67. 'notes': """
  68. Transactions with either {pnm} or non-{pnm} input addresses may be signed.
  69. For non-{pnm} inputs, the bitcoind wallet.dat is used as the key source.
  70. For {pnm} inputs, key data is generated from your seed as with the
  71. {pnl}-addrgen and {pnl}-keygen utilities.
  72. Data for the --from-<what> options will be taken from a file if a second
  73. file is specified on the command line. Otherwise, the user will be
  74. prompted to enter the data.
  75. In cases of transactions with mixed {pnm} and non-{pnm} inputs, non-{pnm}
  76. keys must be supplied in a separate file (WIF format, one key per line)
  77. using the '--keys-from-file' option. Alternatively, one may get keys from
  78. a running bitcoind using the '--force-wallet-dat' option. First import the
  79. required {pnm} keys using 'bitcoind importprivkey'.
  80. For transaction outputs that are {pnm} addresses, {pnm}-to-Bitcoin address
  81. mappings are verified. Therefore, seed material or a key-address file for
  82. these addresses must be supplied on the command line.
  83. Seed data supplied in files must have the following extensions:
  84. wallet: '.{w.ext}'
  85. seed: '.{s.ext}'
  86. mnemonic: '.{m.ext}'
  87. brainwallet: '.{b.ext}'
  88. FMT CODES:
  89. {f}
  90. """.format(
  91. f='\n '.join(SeedSource.format_fmt_codes().splitlines()),
  92. pnm=pnm,pnl=pnm.lower(),
  93. w=Wallet,s=SeedFile,m=Mnemonic,b=Brainwallet
  94. )
  95. }
  96. wmsg = {
  97. 'mapping_error': """
  98. {pnm} -> BTC address mappings differ!
  99. %-23s %s -> %s
  100. %-23s %s -> %s
  101. """.strip().format(pnm=pnm),
  102. 'missing_keys_error': """
  103. A key file must be supplied for the following non-{pnm} address%s:\n %s
  104. """.format(pnm=pnm).strip()
  105. }
  106. def get_seed_for_seed_id(seed_id,infiles,saved_seeds):
  107. if seed_id in saved_seeds:
  108. return saved_seeds[seed_id]
  109. while True:
  110. if infiles:
  111. ss = SeedSource(infiles.pop(0),ignore_in_fmt=True)
  112. elif opt.in_fmt:
  113. qmsg('Need seed data for Seed ID %s' % seed_id)
  114. ss = SeedSource()
  115. msg('User input produced Seed ID %s' % ss.seed.sid)
  116. else:
  117. die(2,'ERROR: No seed source found for Seed ID: %s' % seed_id)
  118. saved_seeds[ss.seed.sid] = ss.seed
  119. if ss.seed.sid == seed_id: return ss.seed
  120. def generate_keys_for_mmgen_addrs(mmgen_addrs,infiles,saved_seeds):
  121. seed_ids = set([i[:8] for i in mmgen_addrs])
  122. vmsg('Need seed%s: %s' % (suf(seed_ids,'k'),' '.join(seed_ids)))
  123. d = []
  124. from mmgen.addr import KeyAddrList
  125. for seed_id in seed_ids:
  126. # Returns only if seed is found
  127. seed = get_seed_for_seed_id(seed_id,infiles,saved_seeds)
  128. addr_idxs = AddrIdxList(idx_list=[int(i[9:]) for i in mmgen_addrs if i[:8] == seed_id])
  129. d += KeyAddrList(seed=seed,addr_idxs=addr_idxs,do_chksum=False).flat_list()
  130. return d
  131. def add_keys(tx,src,infiles=None,saved_seeds=None,keyaddr_list=None):
  132. need_keys = [e for e in getattr(tx,src) if e.mmid and not e.have_wif]
  133. if not need_keys: return []
  134. desc,m1 = ('key-address file','From key-address file:') if keyaddr_list else \
  135. ('seed(s)','Generated from seed:')
  136. qmsg('Checking {} -> BTC address mappings for {} (from {})'.format(pnm,src,desc))
  137. d = keyaddr_list.flat_list() if keyaddr_list else \
  138. generate_keys_for_mmgen_addrs([e.mmid for e in need_keys],infiles,saved_seeds)
  139. new_keys = []
  140. for e in need_keys:
  141. for f in d:
  142. if f.mmid == e.mmid:
  143. if f.addr == e.addr:
  144. e.have_wif = True
  145. if src == 'inputs':
  146. new_keys.append(f.wif)
  147. else:
  148. die(3,wmsg['mapping_error'] % (m1,f.mmid,f.addr,'tx file:',e.mmid,e.addr))
  149. if new_keys:
  150. vmsg('Added %s wif key%s from %s' % (len(new_keys),suf(new_keys,'k'),desc))
  151. return new_keys
  152. # # functions unneeded - use bitcoin-cli walletdump instead
  153. # def get_bitcoind_passphrase(prompt):
  154. # if opt.passwd_file:
  155. # pwfile_reuse_warning()
  156. # return get_data_from_file(opt.passwd_file,'passphrase').strip('\r\n')
  157. # else:
  158. # return my_raw_input(prompt, echo=opt.echo_passphrase)
  159. #
  160. # def sign_tx_with_bitcoind_wallet(c,tx,tx_num_str,keys):
  161. # ok = tx.sign(c,tx_num_str,keys) # returns false on failure
  162. # if ok:
  163. # return ok
  164. # else:
  165. # msg('Using keys in wallet.dat as per user request')
  166. # prompt = 'Enter passphrase for bitcoind wallet: '
  167. # while True:
  168. # passwd = get_bitcoind_passphrase(prompt)
  169. # ret = c.walletpassphrase(passwd, 9999,on_fail='return')
  170. # if rpc_error(ret):
  171. # if rpc_errmsg(ret,'unencrypted wallet, but walletpassphrase was called'):
  172. # msg('Wallet is unencrypted'); break
  173. # else:
  174. # msg('Passphrase OK'); break
  175. #
  176. # ok = tx.sign(c,tx_num_str,keys)
  177. #
  178. # msg('Locking wallet')
  179. # ret = c.walletlock(on_fail='return')
  180. # if rpc_error(ret):
  181. # msg('Failed to lock wallet')
  182. #
  183. # return ok
  184. # main(): execution begins here
  185. infiles = opts.init(opts_data,add_opts=['b16'])
  186. if not infiles: opts.usage()
  187. for i in infiles: check_infile(i)
  188. c = bitcoin_connection()
  189. saved_seeds = {}
  190. tx_files = [i for i in infiles if get_extension(i) == MMGenTX.raw_ext]
  191. seed_files = [i for i in infiles if get_extension(i) in SeedSource.get_extensions()]
  192. if not tx_files:
  193. die(1,'You must specify a raw transaction file!')
  194. if not (seed_files or opt.mmgen_keys_from_file or opt.keys_from_file): # or opt.use_wallet_dat):
  195. die(1,'You must specify a seed or key source!')
  196. if not opt.info and not opt.terse_info:
  197. do_license_msg(immed=True)
  198. kal,kl = None,None
  199. if opt.mmgen_keys_from_file:
  200. kal = KeyAddrList(opt.mmgen_keys_from_file)
  201. if opt.keys_from_file:
  202. l = get_lines_from_file(opt.keys_from_file,'key-address data',trim_comments=True)
  203. kl = KeyAddrList(keylist=[m.split()[0] for m in l]) # accept bitcoind wallet dumps
  204. if kal: kl.remove_dups(kal,key='wif')
  205. kl.generate_addrs()
  206. tx_num_str = ''
  207. for tx_num,tx_file in enumerate(tx_files,1):
  208. if len(tx_files) > 1:
  209. msg('\nTransaction #%s of %s:' % (tx_num,len(tx_files)))
  210. tx_num_str = ' #%s' % tx_num
  211. tx = MMGenTX(tx_file)
  212. if tx.check_signed(c):
  213. die(1,'Transaction is already signed!')
  214. vmsg("Successfully opened transaction file '%s'" % tx_file)
  215. if opt.tx_id: die(0,tx.txid)
  216. if opt.info or opt.terse_info:
  217. tx.view(pause=False,terse=opt.terse_info)
  218. sys.exit()
  219. tx.view_with_prompt('View data for transaction%s?' % tx_num_str)
  220. # Start
  221. keys = []
  222. non_mm_addrs = tx.get_non_mmaddrs('inputs')
  223. if non_mm_addrs:
  224. tmp = KeyAddrList(addrlist=non_mm_addrs,do_chksum=False)
  225. tmp.add_wifs(kl)
  226. m = tmp.list_missing('wif')
  227. if m: die(2,wmsg['missing_keys_error'] % (suf(m,'es'),'\n '.join(m)))
  228. keys += tmp.get_wifs()
  229. if opt.mmgen_keys_from_file:
  230. keys += add_keys(tx,'inputs',keyaddr_list=kal)
  231. add_keys(tx,'outputs',keyaddr_list=kal)
  232. keys += add_keys(tx,'inputs',seed_files,saved_seeds)
  233. add_keys(tx,'outputs',seed_files,saved_seeds)
  234. tx.delete_attrs('inputs','have_wif')
  235. tx.delete_attrs('outputs','have_wif')
  236. extra_sids = set(saved_seeds) - tx.get_input_sids()
  237. if extra_sids:
  238. msg('Unused Seed ID%s: %s' %
  239. (suf(extra_sids,'k'),' '.join(extra_sids)))
  240. # if opt.use_wallet_dat:
  241. # ok = sign_tx_with_bitcoind_wallet(c,tx,tx_num_str,keys)
  242. # else:
  243. ok = tx.sign(c,tx_num_str,keys)
  244. if ok:
  245. tx.add_comment() # edits an existing comment
  246. tx.write_to_file(ask_write_default_yes=True,add_desc=tx_num_str)
  247. else:
  248. die(3,'failed\nSome keys were missing. Transaction %scould not be signed.' % tx_num_str)