test.py 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969
  1. #!/usr/bin/python
  2. # Chdir to repo root.
  3. # Since script is not in repo root, fix sys.path so that modules are
  4. # imported from repo, not system.
  5. import sys,os
  6. pn = os.path.dirname(sys.argv[0])
  7. os.chdir(os.path.join(pn,os.pardir))
  8. sys.path.__setitem__(0,os.path.abspath(os.curdir))
  9. hincog_fn = "rand_data"
  10. non_mmgen_fn = "btckey"
  11. from collections import OrderedDict
  12. cmd_data = OrderedDict([
  13. # test description depends
  14. ['walletgen', (1,'wallet generation', [[[],1]])],
  15. ['walletchk', (1,'wallet check', [[["mmdat"],1]])],
  16. ['addrgen', (1,'address generation', [[["mmdat"],1]])],
  17. ['addrimport', (1,'address import', [[["addrs"],1]])],
  18. ['txcreate', (1,'transaction creation', [[["addrs"],1]])],
  19. ['txsign', (1,'transaction signing', [[["mmdat","raw"],1]])],
  20. ['txsend', (1,'transaction sending', [[["sig"],1]])],
  21. ['export_seed', (1,'seed export to mmseed format', [[["mmdat"],1]])],
  22. ['export_mnemonic', (1,'seed export to mmwords format', [[["mmdat"],1]])],
  23. ['export_incog', (1,'seed export to mmincog format', [[["mmdat"],1]])],
  24. ['export_incog_hex',(1,'seed export to mmincog hex format', [[["mmdat"],1]])],
  25. ['export_incog_hidden',(1,'seed export to hidden mmincog format', [[["mmdat"],1]])],
  26. ['addrgen_seed', (1,'address generation from mmseed file', [[["mmseed","addrs"],1]])],
  27. ['addrgen_mnemonic',(1,'address generation from mmwords file',[[["mmwords","addrs"],1]])],
  28. ['addrgen_incog', (1,'address generation from mmincog file',[[["mmincog","addrs"],1]])],
  29. ['addrgen_incog_hex',(1,'address generation from mmincog hex file',[[["mmincox","addrs"],1]])],
  30. ['addrgen_incog_hidden',(1,'address generation from hidden mmincog file', [[[hincog_fn,"addrs"],1]])],
  31. ['keyaddrgen', (1,'key-address file generation', [[["mmdat"],1]])],
  32. ['txsign_keyaddr',(1,'transaction signing with key-address file', [[["akeys.mmenc","raw"],1]])],
  33. ['walletgen2',(2,'wallet generation (2)', [])],
  34. ['addrgen2', (2,'address generation (2)', [[["mmdat"],2]])],
  35. ['txcreate2', (2,'transaction creation (2)', [[["addrs"],2]])],
  36. ['txsign2', (2,'transaction signing, two transactions',[[["mmdat","raw"],1],[["mmdat","raw"],2]])],
  37. ['export_mnemonic2', (2,'seed export to mmwords format (2)',[[["mmdat"],2]])],
  38. ['walletgen3',(3,'wallet generation (3)', [])],
  39. ['addrgen3', (3,'address generation (3)', [[["mmdat"],3]])],
  40. ['txcreate3', (3,'tx creation with inputs and outputs from two wallets', [[["addrs"],1],[["addrs"],3]])],
  41. ['txsign3', (3,'tx signing with inputs and outputs from two wallets',[[["mmdat"],1],[["mmdat","raw"],3]])],
  42. ['walletgen4',(4,'wallet generation (4) (brainwallet)', [])],
  43. ['addrgen4', (4,'address generation (4)', [[["mmdat"],4]])],
  44. ['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, plus non-MMGen inputs and outputs', [[["addrs"],1],[["addrs"],2],[["addrs"],3],[["addrs"],4]])],
  45. ['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet and brainwallet, plus non-MMGen inputs and outputs', [[["mmincog"],1],[["mmwords"],2],[["mmdat"],3],[["mmbrain","raw",non_mmgen_fn],4]])],
  46. ])
  47. utils = {
  48. 'check_deps': 'check dependencies for specified command, deleting out-of-date files',
  49. 'clean': 'clean specified tmp dir(s) (1,2,3,4; no arg = all tmpdirs)',
  50. }
  51. addrs_per_wallet = 8
  52. cfgs = {
  53. '1': {
  54. 'tmpdir': "test/tmp1",
  55. 'wpasswd': "Dorian",
  56. 'kapasswd': "Grok the blockchain",
  57. 'addr_idx_list': "12,99,5-10,5,12", # 8 addresses
  58. 'dep_generators': {
  59. 'mmdat': "walletgen",
  60. 'addrs': "addrgen",
  61. 'raw': "txcreate",
  62. 'sig': "txsign",
  63. 'mmwords': "export_mnemonic",
  64. 'mmseed': "export_seed",
  65. 'mmincog': "export_incog",
  66. 'mmincox': "export_incog_hex",
  67. hincog_fn: "export_incog_hidden",
  68. 'akeys.mmenc': "keyaddrgen"
  69. },
  70. },
  71. '2': {
  72. 'tmpdir': "test/tmp2",
  73. 'wpasswd': "Hodling away",
  74. 'addr_idx_list': "37,45,3-6,22-23", # 8 addresses
  75. 'dep_generators': {
  76. 'mmdat': "walletgen2",
  77. 'addrs': "addrgen2",
  78. 'raw': "txcreate2",
  79. 'sig': "txsign2",
  80. 'mmwords': "export_mnemonic2",
  81. },
  82. },
  83. '3': {
  84. 'tmpdir': "test/tmp3",
  85. 'wpasswd': "Major miner",
  86. 'addr_idx_list': "73,54,1022-1023,2-5", # 8 addresses
  87. 'dep_generators': {
  88. 'mmdat': "walletgen3",
  89. 'addrs': "addrgen3",
  90. 'raw': "txcreate3",
  91. 'sig': "txsign3"
  92. },
  93. },
  94. '4': {
  95. 'tmpdir': "test/tmp4",
  96. 'wpasswd': "Hashrate rising",
  97. 'addr_idx_list': "63,1004,542-544,7-9", # 8 addresses
  98. 'dep_generators': {
  99. 'mmdat': "walletgen4",
  100. 'mmbrain': "walletgen4",
  101. 'addrs': "addrgen4",
  102. 'raw': "txcreate4",
  103. 'sig': "txsign4",
  104. non_mmgen_fn: "txcreate4"
  105. },
  106. 'bw_filename': "brainwallet.mmbrain",
  107. 'bw_params': "256,1",
  108. },
  109. }
  110. cfg = cfgs['1']
  111. from binascii import hexlify
  112. def getrand(n): return int(hexlify(os.urandom(n)),16)
  113. def msgrepr(d): sys.stderr.write(repr(d)+"\n")
  114. def msgrepr_exit(d):
  115. sys.stderr.write(repr(d)+"\n")
  116. sys.exit()
  117. # total of two outputs must be < 10 BTC
  118. for k in cfgs.keys():
  119. cfgs[k]['amts'] = [0,0]
  120. for idx,mod in (0,6),(1,4):
  121. cfgs[k]['amts'][idx] = "%s.%s" % ((getrand(2) % mod), str(getrand(4))[:5])
  122. meta_cmds = OrderedDict([
  123. ['gen', (1,("walletgen","walletchk","addrgen"))],
  124. ['tx', (1,("txcreate","txsign","txsend"))],
  125. ['export', (1,[k for k in cmd_data if k[:7] == "export_" and cmd_data[k][0] == 1])],
  126. ['gen_sp', (1,[k for k in cmd_data if k[:8] == "addrgen_" and cmd_data[k][0] == 1])],
  127. ['online', (1,("keyaddrgen","txsign_keyaddr"))],
  128. ['2', (2,[k for k in cmd_data if cmd_data[k][0] == 2])],
  129. ['3', (3,[k for k in cmd_data if cmd_data[k][0] == 3])],
  130. ['4', (4,[k for k in cmd_data if cmd_data[k][0] == 4])],
  131. ])
  132. from mmgen.Opts import *
  133. help_data = {
  134. 'prog_name': "test.py",
  135. 'desc': "Test suite for the MMGen suite",
  136. 'usage':"[options] [command or metacommand]",
  137. 'options': """
  138. -h, --help Print this help message
  139. -b, --buf-keypress Use buffered keypresses as with real human input
  140. -d, --debug Produce debugging output
  141. -e, --exact-output Show the exact output of the MMGen script(s) being run
  142. -l, --list-cmds List and describe the tests and commands in the test suite
  143. -p, --pause Pause between tests, resuming on keypress
  144. -q, --quiet Produce minimal output. Suppress dependency info
  145. -s, --system Test scripts and modules installed on system rather than those in the repo root
  146. -v, --verbose Produce more verbose output
  147. """,
  148. 'notes': """
  149. If no command is given, the whole suite of tests is run.
  150. """
  151. }
  152. opts,cmd_args = parse_opts(sys.argv,help_data)
  153. if 'system' in opts: sys.path.pop(0)
  154. env = os.environ
  155. if 'buf_keypress' in opts:
  156. send_delay = 0.3
  157. else:
  158. send_delay = 0
  159. env["MMGEN_DISABLE_HOLD_PROTECT"] = "1"
  160. for k in 'debug','verbose','exact_output','pause','quiet':
  161. globals()[k] = True if k in opts else False
  162. if debug: verbose = True
  163. if exact_output:
  164. def msg(s): pass
  165. vmsg = vmsg_r = msg_r = msg
  166. else:
  167. def msg(s): sys.stderr.write(s+"\n")
  168. def vmsg(s):
  169. if verbose: sys.stderr.write(s+"\n")
  170. def msg_r(s): sys.stderr.write(s)
  171. def vmsg_r(s):
  172. if verbose: sys.stderr.write(s)
  173. stderr_save = sys.stderr
  174. def silence():
  175. if not (verbose or exact_output):
  176. sys.stderr = open("/dev/null","a")
  177. def end_silence():
  178. if not (verbose or exact_output):
  179. sys.stderr = stderr_save
  180. def errmsg(s): stderr_save.write(s+"\n")
  181. def Msg(s): sys.stdout.write(s+"\n")
  182. if "list_cmds" in opts:
  183. Msg("Available commands:")
  184. w = max([len(i) for i in cmd_data])
  185. for cmd in cmd_data:
  186. Msg(" {:<{w}} - {}".format(cmd,cmd_data[cmd][1],w=w))
  187. Msg("\nAvailable metacommands:")
  188. w = max([len(i) for i in meta_cmds])
  189. for cmd in meta_cmds:
  190. Msg(" {:<{w}} - {}".format(cmd," + ".join(meta_cmds[cmd][1]),w=w))
  191. Msg("\nAvailable utilities:")
  192. w = max([len(i) for i in utils])
  193. for cmd in sorted(utils):
  194. Msg(" {:<{w}} - {}".format(cmd,utils[cmd],w=w))
  195. sys.exit()
  196. import pexpect,time,re
  197. import mmgen.config as g
  198. from mmgen.util import get_data_from_file, write_to_file, get_lines_from_file
  199. redc,grnc,yelc,cyac,reset = (
  200. ["\033[%sm" % c for c in "31;1","32;1","33;1","36;1","0"]
  201. )
  202. def red(s): return redc+s+reset
  203. def green(s): return grnc+s+reset
  204. def yellow(s): return yelc+s+reset
  205. def cyan(s): return cyac+s+reset
  206. def my_send(p,t,delay=send_delay,s=False):
  207. if delay: time.sleep(delay)
  208. ret = p.send(t) # returns num bytes written
  209. if delay: time.sleep(delay)
  210. if verbose:
  211. ls = "" if debug or not s else " "
  212. es = "" if s else " "
  213. msg("%sSEND %s%s" % (ls,es,yellow("'%s'"%t.replace('\n',r'\n'))))
  214. return ret
  215. def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False):
  216. quo = "'" if type(s) == str else ""
  217. if verbose: msg_r("EXPECT %s" % yellow(quo+str(s)+quo))
  218. else: msg_r("+")
  219. try:
  220. if s == '': ret = 0
  221. else:
  222. f = p.expect if regex else p.expect_exact
  223. ret = f(s,timeout=3)
  224. except pexpect.TIMEOUT:
  225. errmsg(red("\nERROR. Expect %s%s%s timed out. Exiting" % (quo,s,quo)))
  226. sys.exit(1)
  227. if debug or (verbose and type(s) != str): msg_r(" ==> %s " % ret)
  228. if ret == -1:
  229. errmsg("Error. Expect returned %s" % ret)
  230. sys.exit(1)
  231. else:
  232. if t == '':
  233. if not nonl: vmsg("")
  234. else: ret = my_send(p,t,delay,s)
  235. return ret
  236. def cleandir(d):
  237. try: files = os.listdir(d)
  238. except: return
  239. msg(green("Cleaning directory '%s'" % d))
  240. for f in files:
  241. os.unlink(os.path.join(d,f))
  242. def get_file_with_ext(ext,mydir,delete=False):
  243. flist = [os.path.join(mydir,f)
  244. for f in os.listdir(mydir) if f.split(".")[-1] == ext]
  245. if not flist:
  246. flist = [os.path.join(mydir,f)
  247. for f in os.listdir(mydir) if ".".join(f.split(".")[-2:]) == ext]
  248. if not flist:
  249. return False
  250. if len(flist) > 1 or delete:
  251. if not quiet:
  252. msg("Multiple *.%s files in '%s' - deleting" % (ext,mydir))
  253. for f in flist: os.unlink(f)
  254. return False
  255. else:
  256. return flist[0]
  257. def get_addrfile_checksum(display=False):
  258. addrfile = get_file_with_ext("addrs",cfg['tmpdir'])
  259. silence()
  260. from mmgen.tx import parse_addrfile
  261. chk = parse_addrfile(addrfile,{},return_chk_and_sid=True)[0]
  262. if verbose and display: msg("Checksum: %s" % cyan(chk))
  263. end_silence()
  264. return chk
  265. def verify_checksum_or_exit(checksum,chk):
  266. if checksum != chk:
  267. errmsg(red("Checksum error: %s" % chk))
  268. sys.exit(1)
  269. vmsg(green("Checksums match: %s") % (cyan(chk)))
  270. class MMGenExpect(object):
  271. def __init__(self,name,mmgen_cmd,cmd_args=[],env=env):
  272. if not 'system' in opts:
  273. mmgen_cmd = os.path.join(os.curdir,mmgen_cmd)
  274. desc = cmd_data[name][1]
  275. if verbose or exact_output:
  276. sys.stderr.write(
  277. green("Testing %s\nExecuting " % desc) +
  278. cyan("'%s %s'\n" % (mmgen_cmd," ".join(cmd_args)))
  279. )
  280. else:
  281. msg_r("Testing %s " % (desc+":"))
  282. if env: self.p = pexpect.spawn(mmgen_cmd,cmd_args,env=env)
  283. else: self.p = pexpect.spawn(mmgen_cmd,cmd_args)
  284. if exact_output: self.p.logfile = sys.stdout
  285. def license(self):
  286. p = "'w' for conditions and warranty info, or 'c' to continue: "
  287. my_expect(self.p,p,'c')
  288. def usr_rand(self,num_chars):
  289. rand_chars = [chr(ord(i)%94+33) for i in list(os.urandom(num_chars))]
  290. my_expect(self.p,'symbols left: ','x')
  291. try:
  292. vmsg_r("SEND ")
  293. while self.p.expect('left: ',0.1) == 0:
  294. ch = rand_chars.pop(0)
  295. msg_r(yellow(ch)+" " if verbose else "+")
  296. self.p.send(ch)
  297. except:
  298. vmsg("EOT")
  299. my_expect(self.p,"ENTER to continue: ",'\n')
  300. def passphrase_new(self,what,passphrase):
  301. my_expect(self.p,("Enter passphrase for new %s: " % what), passphrase+"\n")
  302. my_expect(self.p,"Repeat passphrase: ", passphrase+"\n")
  303. def passphrase(self,what,passphrase):
  304. my_expect(self.p,("Enter passphrase for %s.*?: " % what),
  305. passphrase+"\n",regex=True)
  306. def hash_preset(self,what,preset=''):
  307. my_expect(self.p,("Enter hash preset for %s, or ENTER .*?:" % what),
  308. str(preset)+"\n",regex=True)
  309. def ok(self):
  310. if verbose or exact_output:
  311. sys.stderr.write(green("OK\n"))
  312. else: msg(" OK")
  313. def written_to_file(self,what,overwrite_unlikely=False,query="Overwrite? "):
  314. s1 = "%s written to file " % what
  315. s2 = query + "Type uppercase 'YES' to confirm: "
  316. ret = my_expect(self.p,s1 if overwrite_unlikely else [s1,s2])
  317. if ret == 1:
  318. my_send(self.p,"YES\n")
  319. ret = my_expect(self.p,s1)
  320. outfile = self.p.readline().strip().strip("'")
  321. vmsg("%s file: %s" % (what,cyan(outfile.replace("'",""))))
  322. return outfile
  323. def no_overwrite(self):
  324. self.expect("Overwrite? Type uppercase 'YES' to confirm: ","\n")
  325. self.expect("Exiting at user request")
  326. def tx_view(self):
  327. my_expect(self.p,r"View .*?transaction.*? \(y\)es, \(N\)o, \(v\)iew in pager: ","\n",regex=True)
  328. def expect_getend(self,s,regex=False):
  329. ret = self.expect(s,regex=regex,nonl=True)
  330. end = self.readline().strip()
  331. vmsg(" ==> %s" % cyan(end))
  332. return end
  333. def interactive(self):
  334. return self.p.interact()
  335. def logfile(self,arg):
  336. self.p.logfile = arg
  337. def expect(self,*args,**kwargs):
  338. return my_expect(self.p,*args,**kwargs)
  339. def send(self,*args,**kwargs):
  340. return my_send(self.p,*args,**kwargs)
  341. def readline(self):
  342. return self.p.readline()
  343. def read(self,n):
  344. return self.p.read(n)
  345. from mmgen.rpc.data import TransactionInfo
  346. from decimal import Decimal
  347. from mmgen.bitcoin import verify_addr
  348. def add_fake_unspent_entry(out,address,comment):
  349. out.append(TransactionInfo(
  350. account = unicode(comment),
  351. vout = (getrand(4) % 8),
  352. txid = unicode(hexlify(os.urandom(32))),
  353. amount = Decimal("%s.%s" % (10+(getrand(4) % 40), getrand(4) % 100000000)),
  354. address = address,
  355. spendable = False,
  356. scriptPubKey = ("76a914"+verify_addr(address,return_hex=True)+"88ac"),
  357. confirmations = getrand(4) % 500
  358. ))
  359. def create_fake_unspent_data(addr_data,unspent_data_file,tx_data,non_mmgen_input=''):
  360. out = []
  361. for s in tx_data.keys():
  362. sid = tx_data[s]['sid']
  363. for idx in addr_data[sid].keys():
  364. address = unicode(addr_data[sid][idx][0])
  365. add_fake_unspent_entry(out,address, "%s:%s Test Wallet" % (sid,idx))
  366. if non_mmgen_input:
  367. from mmgen.bitcoin import privnum2addr,hextowif
  368. privnum = getrand(32)
  369. btcaddr = privnum2addr(privnum,compressed=True)
  370. of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
  371. write_to_file(of, hextowif("{:064x}".format(privnum),
  372. compressed=True)+"\n",{},"compressed bitcoin key")
  373. add_fake_unspent_entry(out,btcaddr,"Non-MMGen address")
  374. write_to_file(unspent_data_file,repr(out),{},"Unspent outputs",verbose=True)
  375. def add_comments_to_addr_file(addrfile,tfile):
  376. silence()
  377. msg(green("Adding comments to address file '%s'" % addrfile))
  378. d = get_lines_from_file(addrfile)
  379. addr_data = {}
  380. from mmgen.tx import parse_addrfile
  381. parse_addrfile(addrfile,addr_data)
  382. sid = addr_data.keys()[0]
  383. def s(k): return int(k)
  384. keys = sorted(addr_data[sid].keys(),key=s)
  385. for n,k in enumerate(keys,1):
  386. addr_data[sid][k][1] = ("Test address " + str(n))
  387. d = "#\n# Test address file with comments\n#\n%s {\n%s\n}\n" % (sid,
  388. "\n".join([" {:<3} {:<36} {}".format(k,*addr_data[sid][k]) for k in keys]))
  389. msg_r(d)
  390. write_to_file(tfile,d,{})
  391. end_silence()
  392. def make_brainwallet_file(fn):
  393. # Print random words with random whitespace in between
  394. from mmgen.mn_tirosh import tirosh_words
  395. wl = tirosh_words.split("\n")
  396. nwords,ws_list,max_spaces = 10," \n",5
  397. def rand_ws_seq():
  398. nchars = getrand(1) % max_spaces + 1
  399. return "".join([ws_list[getrand(1)%len(ws_list)] for i in range(nchars)])
  400. rand_pairs = [wl[getrand(4) % len(wl)] + rand_ws_seq() for i in range(nwords)]
  401. d = "".join(rand_pairs).rstrip() + "\n"
  402. if verbose: msg_r("Brainwallet password:\n%s" % cyan(d))
  403. write_to_file(fn,d,{},"brainwallet password")
  404. def do_between():
  405. if pause:
  406. from mmgen.util import keypress_confirm
  407. if keypress_confirm(green("Continue?"),default_yes=True):
  408. if verbose or exact_output: sys.stderr.write("\n")
  409. else:
  410. errmsg("Exiting at user request")
  411. sys.exit()
  412. elif verbose or exact_output:
  413. sys.stderr.write("\n")
  414. def do_cmd(ts,cmd):
  415. al = []
  416. for exts,idx in cmd_data[cmd][2]:
  417. global cfg
  418. cfg = cfgs[str(idx)]
  419. for ext in exts:
  420. while True:
  421. infile = get_file_with_ext(ext,cfg['tmpdir'])
  422. if infile:
  423. al.append(infile); break
  424. else:
  425. dg = cfg['dep_generators'][ext]
  426. if not quiet: msg("Need *.%s from '%s'" % (ext,dg))
  427. do_cmd(ts,dg)
  428. do_between()
  429. MMGenTestSuite.__dict__[cmd](*([ts,cmd] + al))
  430. hincog_bytes = 1024*1024
  431. hincog_offset = 98765
  432. hincog_seedlen = 256
  433. rebuild_list = OrderedDict()
  434. def check_if_needs_rebuild(num,ext):
  435. ret = False
  436. fn = get_file_with_ext(ext,cfgs[num]['tmpdir'])
  437. if not fn: ret = True
  438. cmd = cfgs[num]['dep_generators'][ext]
  439. deps = [(str(n),e) for exts,n in cmd_data[cmd][2] for e in exts]
  440. if fn:
  441. my_age = os.stat(fn).st_mtime
  442. for num,ext in deps:
  443. f = get_file_with_ext(ext,cfgs[num]['tmpdir'])
  444. if f and os.stat(f).st_mtime > my_age: ret = True
  445. for num,ext in deps:
  446. if check_if_needs_rebuild(num,ext): ret = True
  447. if ret and fn:
  448. if not quiet: msg("File '%s' out of date - deleting" % fn)
  449. os.unlink(fn)
  450. rebuild_list[cmd] = ret
  451. return ret
  452. class MMGenTestSuite(object):
  453. def __init__(self):
  454. pass
  455. def check_deps(self,name,cmds):
  456. if len(cmds) != 1:
  457. msg("Usage: %s check_deps <command>" % g.prog_name)
  458. sys.exit(1)
  459. cmd = cmds[0]
  460. if cmd not in cmd_data:
  461. msg("'%s': unrecognized command" % cmd)
  462. sys.exit(1)
  463. d = [(str(num),ext) for exts,num in cmd_data[cmd][2] for ext in exts]
  464. if not quiet:
  465. w = "Checking" if d else "No"
  466. msg("%s dependencies for '%s'" % (w,cmd))
  467. for num,ext in d:
  468. check_if_needs_rebuild(num,ext)
  469. if debug:
  470. for cmd in rebuild_list:
  471. msg("cmd: %-15s rebuild: %s" %
  472. (cmd, cyan("Yes") if rebuild_list[cmd] else "No"))
  473. def clean(self,name,dirs=[]):
  474. dirlist = dirs if dirs else cfgs.keys()
  475. for k in dirlist:
  476. if k in cfgs:
  477. cleandir(cfgs[k]['tmpdir'])
  478. else:
  479. msg("%s: invalid directory index" % k)
  480. sys.exit(1)
  481. def walletgen(self,name,brain=False):
  482. try: os.mkdir(cfg['tmpdir'],0755)
  483. except OSError as e:
  484. if e.errno != 17: raise
  485. else: msg("Created directory '%s'" % cfg['tmpdir'])
  486. # cleandir(cfg['tmpdir'])
  487. args = ["-d",cfg['tmpdir'],"-p1","-r10"]
  488. if brain:
  489. bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename'])
  490. args += ["-b",cfg['bw_params'],bwf]
  491. make_brainwallet_file(bwf)
  492. t = MMGenExpect(name,"mmgen-walletgen", args)
  493. t.license()
  494. if brain:
  495. t.expect(
  496. "A brainwallet will be secure only if you really know what you're doing")
  497. t.expect("Type uppercase 'YES' to confirm: ","YES\n")
  498. t.usr_rand(10)
  499. t.expect("Generating a key from OS random data plus user entropy")
  500. if not brain:
  501. t.expect("Generating a key from OS random data plus saved user entropy")
  502. t.passphrase_new("MMGen wallet",cfg['wpasswd'])
  503. t.written_to_file("Wallet")
  504. t.ok()
  505. def walletchk_beg(self,name,args):
  506. t = MMGenExpect(name,"mmgen-walletchk", args)
  507. t.expect("Getting MMGen wallet data from file '%s'" % args[-1])
  508. t.passphrase("MMGen wallet",cfg['wpasswd'])
  509. t.expect("Passphrase is OK")
  510. t.expect("Wallet is OK")
  511. return t
  512. def walletchk(self,name,walletfile):
  513. t = self.walletchk_beg(name,[walletfile])
  514. t.ok()
  515. def addrgen(self,name,walletfile):
  516. t = MMGenExpect(name,"mmgen-addrgen",["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
  517. t.license()
  518. t.passphrase("MMGen wallet",cfg['wpasswd'])
  519. t.expect("Passphrase is OK")
  520. t.expect("Generated [0-9]+ addresses",regex=True)
  521. t.expect_getend(r"Checksum for address data .*?: ",regex=True)
  522. t.written_to_file("Addresses")
  523. t.ok()
  524. def addrimport(self,name,addrfile):
  525. outfile = os.path.join(cfg['tmpdir'],"addrfile_w_comments")
  526. add_comments_to_addr_file(addrfile,outfile)
  527. t = MMGenExpect(name,"mmgen-addrimport",[outfile])
  528. t.expect_getend(r"checksum for addr data .*\[.*\]: ",regex=True)
  529. t.expect_getend("Validating addresses...OK. ")
  530. t.expect("Type uppercase 'YES' to confirm: ","\n")
  531. vmsg("This is a simulation, so no addresses were actually imported into the tracking\nwallet")
  532. t.ok()
  533. def txcreate(self,name,addrfile):
  534. self.txcreate_common(name,sources=['1'])
  535. def txcreate_common(self,name,sources=['1'],non_mmgen_input=''):
  536. if verbose or exact_output:
  537. sys.stderr.write(green("Generating fake transaction info\n"))
  538. silence()
  539. tx_data,addr_data = {},{}
  540. from mmgen.tx import parse_addrfile
  541. from mmgen.util import parse_addr_idxs
  542. for s in sources:
  543. afile = get_file_with_ext("addrs",cfgs[s]["tmpdir"])
  544. chk,sid = parse_addrfile(afile,addr_data,return_chk_and_sid=True)
  545. aix = parse_addr_idxs(cfgs[s]['addr_idx_list'])
  546. if len(aix) != addrs_per_wallet:
  547. errmsg(red("Addr index list length != %s: %s" %
  548. (addrs_per_wallet,repr(aix))))
  549. sys.exit()
  550. tx_data[s] = {
  551. 'addrfile': get_file_with_ext("addrs",cfgs[s]['tmpdir']),
  552. 'chk': chk,
  553. 'sid': sid,
  554. 'addr_idxs': aix[-2:],
  555. }
  556. unspent_data_file = os.path.join(cfg['tmpdir'],"unspent.json")
  557. create_fake_unspent_data(addr_data,unspent_data_file,tx_data,non_mmgen_input)
  558. # make the command line
  559. from mmgen.bitcoin import privnum2addr
  560. btcaddr = privnum2addr(getrand(32),compressed=True)
  561. cmd_args = ["-d",cfg['tmpdir']]
  562. for num in tx_data.keys():
  563. s = tx_data[num]
  564. cmd_args += [
  565. "%s:%s,%s" % (s['sid'],s['addr_idxs'][0],cfgs[num]['amts'][0]),
  566. ]
  567. # + one BTC address
  568. # + one change address and one BTC address
  569. if num is tx_data.keys()[-1]:
  570. cmd_args += ["%s:%s" % (s['sid'],s['addr_idxs'][1])]
  571. cmd_args += ["%s,%s" % (btcaddr,cfgs[num]['amts'][1])]
  572. for num in tx_data: cmd_args += [tx_data[num]['addrfile']]
  573. env["MMGEN_BOGUS_WALLET_DATA"] = unspent_data_file
  574. end_silence()
  575. if verbose or exact_output: sys.stderr.write("\n")
  576. t = MMGenExpect(name,"mmgen-txcreate",cmd_args,env)
  577. t.license()
  578. for num in tx_data.keys():
  579. t.expect_getend("Getting address data from file ")
  580. from mmgen.addr import fmt_addr_idxs
  581. chk=t.expect_getend(r"Computed checksum for addr data .*?: ",regex=True)
  582. verify_checksum_or_exit(tx_data[num]['chk'],chk)
  583. # not in tracking wallet warning, (1 + num sources) times
  584. if t.expect(["Continue anyway? (y/N): ",
  585. "Unable to connect to bitcoind"]) == 0:
  586. t.send("y")
  587. else:
  588. errmsg(red("Error: unable to connect to bitcoind. Exiting"))
  589. sys.exit(1)
  590. for num in tx_data.keys():
  591. t.expect("Continue anyway? (y/N): ","y")
  592. t.expect(r"'q' = quit sorting, .*?: ","M", regex=True)
  593. t.expect(r"'q' = quit sorting, .*?: ","q", regex=True)
  594. outputs_list = [addrs_per_wallet*i + 1 for i in range(len(tx_data))]
  595. if non_mmgen_input: outputs_list.append(len(tx_data)*addrs_per_wallet + 1)
  596. t.expect("Enter a range or space-separated list of outputs to spend: ",
  597. " ".join([str(i) for i in outputs_list])+"\n")
  598. if non_mmgen_input: t.expect("Accept? (y/N): ","y")
  599. t.expect("OK? (Y/n): ","y")
  600. t.expect("Add a comment to transaction? (y/N): ","\n")
  601. t.tx_view()
  602. t.expect("Save transaction? (Y/n): ","\n")
  603. t.written_to_file("Transaction")
  604. t.ok()
  605. def txsign(self,name,txfile,walletfile):
  606. t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],txfile,walletfile])
  607. t.license()
  608. t.tx_view()
  609. t.passphrase("MMGen wallet",cfg['wpasswd'])
  610. t.expect("Edit transaction comment? (y/N): ","\n")
  611. t.written_to_file("Signed transaction")
  612. t.ok()
  613. def txsend(self,name,sigfile):
  614. t = MMGenExpect(name,"mmgen-txsend", ["-d",cfg['tmpdir'],sigfile])
  615. t.license()
  616. t.tx_view()
  617. t.expect("Edit transaction comment? (y/N): ","\n")
  618. t.expect("Are you sure you want to broadcast this transaction to the network?")
  619. t.expect("Type uppercase 'YES, I REALLY WANT TO DO THIS' to confirm: ","\n")
  620. t.expect("Exiting at user request")
  621. vmsg("This is a simulation, so no transaction was sent")
  622. t.ok()
  623. def export_seed(self,name,walletfile):
  624. t = self.walletchk_beg(name,["-s","-d",cfg['tmpdir'],walletfile])
  625. f = t.written_to_file("Seed data")
  626. silence()
  627. msg("Seed data: %s" % cyan(get_data_from_file(f,"seed data")))
  628. end_silence()
  629. t.ok()
  630. def export_mnemonic(self,name,walletfile):
  631. t = self.walletchk_beg(name,["-m","-d",cfg['tmpdir'],walletfile])
  632. f = t.written_to_file("Mnemonic data")
  633. silence()
  634. msg_r("Mnemonic data: %s" % cyan(get_data_from_file(f,"mnemonic data")))
  635. end_silence()
  636. t.ok()
  637. def export_incog(self,name,walletfile,args=["-g"]):
  638. t = MMGenExpect(name,"mmgen-walletchk",args+["-d",cfg['tmpdir'],"-r","10",walletfile])
  639. t.passphrase("MMGen wallet",cfg['wpasswd'])
  640. t.usr_rand(10)
  641. t.expect_getend("Incog ID: ")
  642. if args[0] == "-G": return t
  643. t.written_to_file("Incognito wallet data",overwrite_unlikely=True)
  644. t.ok()
  645. def export_incog_hex(self,name,walletfile):
  646. self.export_incog(name,walletfile,args=["-X"])
  647. # TODO: make outdir and hidden incog compatible (ignore --outdir and warn user?)
  648. def export_incog_hidden(self,name,walletfile):
  649. rf,rd = os.path.join(cfg['tmpdir'],hincog_fn),os.urandom(hincog_bytes)
  650. vmsg(green("Writing %s bytes of data to file '%s'" % (hincog_bytes,rf)))
  651. write_to_file(rf,rd,{},verbose=verbose)
  652. t = self.export_incog(name,walletfile,args=["-G","%s,%s"%(rf,hincog_offset)])
  653. t.written_to_file("Data",query="")
  654. t.ok()
  655. def addrgen_seed(self,name,walletfile,foo,what="seed data",arg="-s"):
  656. t = MMGenExpect(name,"mmgen-addrgen",
  657. [arg,"-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
  658. t.license()
  659. t.expect_getend("Valid %s for seed ID " % what)
  660. vmsg("Comparing generated checksum with checksum from previous address file")
  661. chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
  662. verify_checksum_or_exit(get_addrfile_checksum(),chk)
  663. t.no_overwrite()
  664. t.ok()
  665. def addrgen_mnemonic(self,name,walletfile,foo):
  666. self.addrgen_seed(name,walletfile,foo,what="mnemonic",arg="-m")
  667. def addrgen_incog(self,name,walletfile,foo,args=["-g"]):
  668. t = MMGenExpect(name,"mmgen-addrgen",args+["-d",
  669. cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
  670. t.license()
  671. t.expect_getend("Incog ID: ")
  672. t.passphrase("MMGen incognito wallet \w{8}", cfg['wpasswd'])
  673. t.hash_preset("incog wallet",'1')
  674. vmsg("Comparing generated checksum with checksum from address file")
  675. chk = t.expect_getend(r"Checksum for address data .*?: ",regex=True)
  676. verify_checksum_or_exit(get_addrfile_checksum(),chk)
  677. t.no_overwrite()
  678. t.ok()
  679. def addrgen_incog_hex(self,name,walletfile,foo):
  680. self.addrgen_incog(name,walletfile,foo,args=["-X"])
  681. def addrgen_incog_hidden(self,name,walletfile,foo):
  682. rf = os.path.join(cfg['tmpdir'],hincog_fn)
  683. self.addrgen_incog(name,walletfile,foo,
  684. args=["-G","%s,%s,%s"%(rf,hincog_offset,hincog_seedlen)])
  685. def keyaddrgen(self,name,walletfile):
  686. t = MMGenExpect(name,"mmgen-keygen",
  687. ["-d",cfg['tmpdir'],walletfile,cfg['addr_idx_list']])
  688. t.license()
  689. t.expect("Type uppercase 'YES' to confirm: ","YES\n")
  690. t.passphrase("MMGen wallet",cfg['wpasswd'])
  691. t.expect_getend(r"Checksum for key-address data .*?: ",regex=True)
  692. t.expect("Encrypt key list? (y/N): ","y")
  693. t.hash_preset("new key list",'1')
  694. t.passphrase_new("key list",cfg['kapasswd'])
  695. t.written_to_file("Keys")
  696. t.ok()
  697. def txsign_keyaddr(self,name,keyaddr_file,txfile):
  698. t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],"-M",keyaddr_file,txfile])
  699. t.license()
  700. t.hash_preset("key-address file",'1')
  701. t.passphrase("key-address file",cfg['kapasswd'])
  702. t.expect("Check key-to-address validity? (y/N): ","y")
  703. t.expect("View data for transaction? (y)es, (N)o, (v)iew in pager: ","\n")
  704. t.expect("Signing transaction...OK")
  705. t.expect("Edit transaction comment? (y/N): ","\n")
  706. t.written_to_file("Signed transaction")
  707. t.ok()
  708. def walletgen2(self,name):
  709. global cfg
  710. cfg = cfgs['2']
  711. self.walletgen(name)
  712. def addrgen2(self,name,walletfile):
  713. self.addrgen(name,walletfile)
  714. def txcreate2(self,name,addrfile):
  715. self.txcreate_common(name,sources=['2'])
  716. def txsign2(self,name,txf1,wf1,txf2,wf2):
  717. t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],txf1,wf1,txf2,wf2])
  718. t.license()
  719. for cnum in ['1','2']:
  720. t.tx_view()
  721. t.passphrase("MMGen wallet",cfgs[cnum]['wpasswd'])
  722. t.expect_getend("Signing transaction ")
  723. t.expect("Edit transaction comment? (y/N): ","\n")
  724. t.written_to_file("Signed transaction #%s" % cnum)
  725. t.ok()
  726. def export_mnemonic2(self,name,walletfile):
  727. self.export_mnemonic(name,walletfile)
  728. def walletgen3(self,name):
  729. global cfg
  730. cfg = cfgs['3']
  731. self.walletgen(name)
  732. def addrgen3(self,name,walletfile):
  733. self.addrgen(name,walletfile)
  734. def txcreate3(self,name,addrfile1,addrfile2):
  735. self.txcreate_common(name,sources=['1','3'])
  736. def txsign3(self,name,wf1,wf2,txf2):
  737. t = MMGenExpect(name,"mmgen-txsign", ["-d",cfg['tmpdir'],wf1,wf2,txf2])
  738. t.license()
  739. t.tx_view()
  740. for s in ['1','3']:
  741. t.expect_getend("Getting MMGen wallet data from file ")
  742. t.passphrase("MMGen wallet",cfgs[s]['wpasswd'])
  743. t.expect_getend("Signing transaction")
  744. t.expect("Edit transaction comment? (y/N): ","\n")
  745. t.written_to_file("Signed transaction")
  746. t.ok()
  747. def walletgen4(self,name):
  748. global cfg
  749. cfg = cfgs['4']
  750. self.walletgen(name,brain=True)
  751. def addrgen4(self,name,walletfile):
  752. self.addrgen(name,walletfile)
  753. def txcreate4(self,name,f1,f2,f3,f4):
  754. self.txcreate_common(name,sources=['1','2','3','4'],non_mmgen_input='4')
  755. def txsign4(self,name,f1,f2,f3,f4,f5,non_mm_fn):
  756. t = MMGenExpect(name,"mmgen-txsign",
  757. ["-d",cfg['tmpdir'],"-b",cfg['bw_params'],"-k",non_mm_fn,f1,f2,f3,f4,f5])
  758. t.license()
  759. t.tx_view()
  760. for cfgnum,what,app in ('1',"incognito"," incognito"),('3',"MMGen",""):
  761. t.expect_getend("Getting %s wallet data from file " % what)
  762. t.passphrase("MMGen%s wallet"%app,cfgs[cfgnum]['wpasswd'])
  763. if cfgnum == '1':
  764. t.hash_preset("incog wallet",'1')
  765. t.expect_getend("Signing transaction")
  766. t.expect("Edit transaction comment? (y/N): ","\n")
  767. t.written_to_file("Signed transaction")
  768. t.ok()
  769. # main()
  770. ts = MMGenTestSuite()
  771. start_time = int(time.time())
  772. if pause:
  773. import termios,atexit
  774. fd = sys.stdin.fileno()
  775. old = termios.tcgetattr(fd)
  776. def at_exit():
  777. termios.tcsetattr(fd, termios.TCSADRAIN, old)
  778. atexit.register(at_exit)
  779. try:
  780. if cmd_args:
  781. arg1 = cmd_args[0]
  782. if arg1 in utils:
  783. if arg1 == "check_deps": debug = True
  784. MMGenTestSuite.__dict__[arg1](ts,arg1,cmd_args[1:])
  785. sys.exit()
  786. elif arg1 in meta_cmds:
  787. if len(cmd_args) == 1:
  788. ts.clean("clean",str(meta_cmds[arg1][0]))
  789. for cmd in meta_cmds[arg1][1]:
  790. do_cmd(ts,cmd)
  791. if cmd is not cmd_data.keys()[-1]: do_between()
  792. else:
  793. msg("Only one meta command may be specified")
  794. sys.exit(1)
  795. elif arg1 in cmd_data:
  796. if len(cmd_args) == 1:
  797. ts.check_deps("check_deps",[arg1])
  798. do_cmd(ts,arg1)
  799. else:
  800. msg("Only one command may be specified")
  801. sys.exit(1)
  802. else:
  803. errmsg("%s: unrecognized command" % arg1)
  804. sys.exit(1)
  805. else:
  806. ts.clean("clean")
  807. for cmd in cmd_data:
  808. do_cmd(ts,cmd)
  809. if cmd is not cmd_data.keys()[-1]: do_between()
  810. except:
  811. sys.stderr = stderr_save
  812. raise
  813. t = int(time.time()) - start_time
  814. msg(green(
  815. "All requested tests finished OK, elapsed time: %02i:%02i" % (t/60,t%60)))