test.py 79 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. # mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
  4. # Copyright (C)2013-2017 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. test/test.py: Test suite for the MMGen suite
  20. """
  21. import sys,os
  22. pn = os.path.dirname(sys.argv[0])
  23. os.chdir(os.path.join(pn,os.pardir))
  24. sys.path.__setitem__(0,os.path.abspath(os.curdir))
  25. # Import these _after_ local path's been added to sys.path
  26. from mmgen.common import *
  27. from mmgen.test import *
  28. g.quiet = False # if 'quiet' was set in config file, disable here
  29. os.environ['MMGEN_QUIET'] = '0' # and for the spawned scripts
  30. tb_cmd = 'scripts/traceback.py'
  31. log_file = 'test.py_log'
  32. scripts = (
  33. 'addrgen', 'addrimport', 'keygen',
  34. 'passchg', 'tool',
  35. 'txcreate', 'txsend', 'txsign',
  36. 'walletchk', 'walletconv', 'walletgen'
  37. )
  38. hincog_fn = 'rand_data'
  39. hincog_bytes = 1024*1024
  40. hincog_offset = 98765
  41. hincog_seedlen = 256
  42. incog_id_fn = 'incog_id'
  43. non_mmgen_fn = 'btckey'
  44. pwfile = 'passwd_file'
  45. ref_dir = os.path.join('test','ref')
  46. ref_wallet_brainpass = 'abc'
  47. ref_wallet_hash_preset = '1'
  48. ref_wallet_incog_offset = 123
  49. from mmgen.obj import MMGenTXLabel
  50. ref_tx_label = ''.join([unichr(i) for i in range(65,91) +
  51. range(1040,1072) + # cyrillic
  52. range(913,939) + # greek
  53. range(97,123)])[:MMGenTXLabel.max_len]
  54. tx_fee = '0.0001'
  55. ref_bw_hash_preset = '1'
  56. ref_bw_file = 'wallet.mmbrain'
  57. ref_bw_file_spc = 'wallet-spaced.mmbrain'
  58. ref_kafile_pass = 'kafile password'
  59. ref_kafile_hash_preset = '1'
  60. ref_enc_fn = 'sample-text.mmenc'
  61. tool_enc_passwd = "Scrypt it, don't hash it!"
  62. sample_text = \
  63. 'The Times 03/Jan/2009 Chancellor on brink of second bailout for banks\n'
  64. # Laggy flash media cause pexpect to crash, so create a temporary directory
  65. # under '/dev/shm' and put datadir and temp files here.
  66. shortopts = ''.join([e[1:] for e in sys.argv if len(e) > 1 and e[0] == '-' and e[1] != '-'])
  67. shortopts = ['-'+e for e in list(shortopts)]
  68. data_dir = os.path.join('test','data_dir')
  69. if not any(e in ('--skip-deps','--resume','-S','-r') for e in sys.argv+shortopts):
  70. if g.platform == 'win':
  71. try: os.listdir(data_dir)
  72. except: pass
  73. else:
  74. import shutil
  75. shutil.rmtree(data_dir)
  76. os.mkdir(data_dir,0755)
  77. else:
  78. d,pfx = '/dev/shm','mmgen-test-'
  79. try:
  80. import subprocess
  81. subprocess.call('rm -rf %s/%s*'%(d,pfx),shell=True)
  82. except Exception as e:
  83. die(2,'Unable to delete directory tree %s/%s* (%s)'%(d,pfx,e))
  84. try:
  85. import tempfile
  86. shm_dir = tempfile.mkdtemp('',pfx,d)
  87. except Exception as e:
  88. die(2,'Unable to create temporary directory in %s (%s)'%(d,e))
  89. dd = os.path.join(shm_dir,'data_dir')
  90. os.mkdir(dd,0755)
  91. try: os.unlink(data_dir)
  92. except: pass
  93. os.symlink(dd,data_dir)
  94. opts_data = {
  95. # 'sets': [('interactive',bool,'verbose',None)],
  96. 'desc': 'Test suite for the MMGen suite',
  97. 'usage':'[options] [command(s) or metacommand(s)]',
  98. 'options': """
  99. -h, --help Print this help message
  100. --, --longhelp Print help message for long options (common options)
  101. -b, --buf-keypress Use buffered keypresses as with real human input
  102. -c, --print-cmdline Print the command line of each spawned command
  103. -d, --debug-scripts Turn on debugging output in executed scripts
  104. -x, --debug-pexpect Produce debugging output for pexpect calls
  105. -D, --direct-exec Bypass pexpect and execute a command directly (for
  106. debugging only)
  107. -e, --exact-output Show the exact output of the MMGen script(s) being run
  108. -g, --segwit Generate and use Segwit addresses
  109. -G, --segwit-random Generate and use a random mix of Segwit and Legacy addrs
  110. -l, --list-cmds List and describe the commands in the test suite
  111. -L, --log Log commands to file {lf}
  112. -n, --names Display command names instead of descriptions
  113. -I, --interactive Interactive mode (without pexpect)
  114. -O, --popen-spawn Use pexpect's popen_spawn instead of popen
  115. -p, --pause Pause between tests, resuming on keypress
  116. -P, --profile Record the execution time of each script
  117. -q, --quiet Produce minimal output. Suppress dependency info
  118. -r, --resume=c Resume at command 'c' after interrupted run
  119. -s, --system Test scripts and modules installed on system rather
  120. than those in the repo root
  121. -S, --skip-deps Skip dependency checking for command
  122. -u, --usr-random Get random data interactively from user
  123. -t, --traceback Run the command inside the '{tb_cmd}' script
  124. -v, --verbose Produce more verbose output
  125. -W, --no-dw-delete Don't remove default wallet from data dir after dw tests are done
  126. """.format(tb_cmd=tb_cmd,lf=log_file),
  127. 'notes': """
  128. If no command is given, the whole suite of tests is run.
  129. """
  130. }
  131. sys.argv = [sys.argv[0]] + ['--data-dir',data_dir] + sys.argv[1:]
  132. cmd_args = opts.init(opts_data)
  133. tn_desc = ('','.testnet')[g.testnet]
  134. def randbool():
  135. return hexlify(os.urandom(1))[1] in '12345678'
  136. def get_segwit_val():
  137. return randbool() if opt.segwit_random else True if opt.segwit else False
  138. cfgs = {
  139. '15': {
  140. 'tmpdir': os.path.join('test','tmp15'),
  141. 'wpasswd': 'Dorian',
  142. 'kapasswd': 'Grok the blockchain',
  143. 'addr_idx_list': '12,99,5-10,5,12', # 8 addresses
  144. 'dep_generators': {
  145. pwfile: 'walletgen_dfl_wallet',
  146. 'addrs': 'addrgen_dfl_wallet',
  147. 'rawtx': 'txcreate_dfl_wallet',
  148. 'sigtx': 'txsign_dfl_wallet',
  149. 'mmseed': 'export_seed_dfl_wallet',
  150. 'del_dw_run': 'delete_dfl_wallet',
  151. },
  152. 'segwit': get_segwit_val()
  153. },
  154. '16': {
  155. 'tmpdir': os.path.join('test','tmp16'),
  156. 'wpasswd': 'My changed password',
  157. 'hash_preset': '2',
  158. 'dep_generators': {
  159. pwfile: 'passchg_dfl_wallet',
  160. },
  161. 'segwit': get_segwit_val()
  162. },
  163. '1': {
  164. 'tmpdir': os.path.join('test','tmp1'),
  165. 'wpasswd': 'Dorian',
  166. 'kapasswd': 'Grok the blockchain',
  167. 'addr_idx_list': '12,99,5-10,5,12', # 8 addresses
  168. 'dep_generators': {
  169. pwfile: 'walletgen',
  170. 'mmdat': 'walletgen',
  171. 'addrs': 'addrgen',
  172. 'rawtx': 'txcreate',
  173. 'txbump': 'txbump',
  174. 'sigtx': 'txsign',
  175. 'mmwords': 'export_mnemonic',
  176. 'mmseed': 'export_seed',
  177. 'mmhex': 'export_hex',
  178. 'mmincog': 'export_incog',
  179. 'mmincox': 'export_incog_hex',
  180. hincog_fn: 'export_incog_hidden',
  181. incog_id_fn: 'export_incog_hidden',
  182. 'akeys.mmenc': 'keyaddrgen'
  183. },
  184. 'segwit': get_segwit_val()
  185. },
  186. '2': {
  187. 'tmpdir': os.path.join('test','tmp2'),
  188. 'wpasswd': 'Hodling away',
  189. 'addr_idx_list': '37,45,3-6,22-23', # 8 addresses
  190. 'seed_len': 128,
  191. 'dep_generators': {
  192. 'mmdat': 'walletgen2',
  193. 'addrs': 'addrgen2',
  194. 'rawtx': 'txcreate2',
  195. 'sigtx': 'txsign2',
  196. 'mmwords': 'export_mnemonic2',
  197. },
  198. 'segwit': get_segwit_val()
  199. },
  200. '3': {
  201. 'tmpdir': os.path.join('test','tmp3'),
  202. 'wpasswd': 'Major miner',
  203. 'addr_idx_list': '73,54,1022-1023,2-5', # 8 addresses
  204. 'dep_generators': {
  205. 'mmdat': 'walletgen3',
  206. 'addrs': 'addrgen3',
  207. 'rawtx': 'txcreate3',
  208. 'sigtx': 'txsign3'
  209. },
  210. 'segwit': get_segwit_val()
  211. },
  212. '4': {
  213. 'tmpdir': os.path.join('test','tmp4'),
  214. 'wpasswd': 'Hashrate good',
  215. 'addr_idx_list': '63,1004,542-544,7-9', # 8 addresses
  216. 'seed_len': 192,
  217. 'dep_generators': {
  218. 'mmdat': 'walletgen4',
  219. 'mmbrain': 'walletgen4',
  220. 'addrs': 'addrgen4',
  221. 'rawtx': 'txcreate4',
  222. 'sigtx': 'txsign4',
  223. 'txdo': 'txdo4',
  224. },
  225. 'bw_filename': 'brainwallet.mmbrain',
  226. 'bw_params': '192,1',
  227. 'segwit': get_segwit_val()
  228. },
  229. '14': {
  230. 'kapasswd': 'Maxwell',
  231. 'tmpdir': os.path.join('test','tmp14'),
  232. 'wpasswd': 'The Halving',
  233. 'addr_idx_list': '61,998,502-504,7-9', # 8 addresses
  234. 'seed_len': 256,
  235. 'dep_generators': {
  236. 'mmdat': 'walletgen14',
  237. 'addrs': 'addrgen14',
  238. 'akeys.mmenc': 'keyaddrgen14',
  239. },
  240. 'segwit': get_segwit_val()
  241. },
  242. '5': {
  243. 'tmpdir': os.path.join('test','tmp5'),
  244. 'wpasswd': 'My changed password',
  245. 'hash_preset': '2',
  246. 'dep_generators': {
  247. 'mmdat': 'passchg',
  248. pwfile: 'passchg',
  249. },
  250. 'segwit': get_segwit_val()
  251. },
  252. '6': {
  253. 'name': 'reference wallet check (128-bit)',
  254. 'seed_len': 128,
  255. 'seed_id': 'FE3C6545',
  256. 'ref_bw_seed_id': '33F10310',
  257. 'addrfile_chk': ('B230 7526 638F 38CB','B64D 7327 EF2A 60FE','9914 6D10 2307 F348','7DBF 441F E188 8B37'),
  258. 'keyaddrfile_chk':('CF83 32FB 8A8B 08E2','FEBF 7878 97BB CC35','C13B F717 D4E8 CF59','4DB5 BAF0 45B7 6E81'),
  259. 'passfile_chk': 'EB29 DC4F 924B 289F',
  260. 'passfile32_chk': '37B6 C218 2ABC 7508',
  261. 'wpasswd': 'reference password',
  262. 'ref_wallet': 'FE3C6545-D782B529[128,1].mmdat',
  263. 'ic_wallet': 'FE3C6545-E29303EA-5E229E30[128,1].mmincog',
  264. 'ic_wallet_hex': 'FE3C6545-BC4BE3F2-32586837[128,1].mmincox',
  265. 'hic_wallet': 'FE3C6545-161E495F-BEB7548E[128,1].incog-offset123',
  266. 'hic_wallet_old': 'FE3C6545-161E495F-9860A85B[128,1].incog-old.offset123',
  267. 'tmpdir': os.path.join('test','tmp6'),
  268. 'kapasswd': '',
  269. 'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses
  270. 'pass_idx_list': '1,4,9-11,1100',
  271. 'dep_generators': {
  272. 'mmdat': 'refwalletgen1',
  273. pwfile: 'refwalletgen1',
  274. 'addrs': 'refaddrgen1',
  275. 'akeys.mmenc': 'refkeyaddrgen1'
  276. },
  277. 'segwit': get_segwit_val()
  278. },
  279. '7': {
  280. 'name': 'reference wallet check (192-bit)',
  281. 'seed_len': 192,
  282. 'seed_id': '1378FC64',
  283. 'ref_bw_seed_id': 'CE918388',
  284. 'addrfile_chk': ('8C17 A5FA 0470 6E89','0A59 C8CD 9439 8B81','91C4 0414 89E4 2089','3BA6 7494 8E2B 858D'),
  285. 'keyaddrfile_chk': ('9648 5132 B98E 3AD9','2F72 C83F 44C5 0FAC','C98B DF08 A3D5 204B','25F2 AEB6 AAAC 8BBE'),
  286. 'passfile_chk': 'ADEA 0083 094D 489A',
  287. 'passfile32_chk': '2A28 C5C7 36EC 217A',
  288. 'wpasswd': 'reference password',
  289. 'ref_wallet': '1378FC64-6F0F9BB4[192,1].mmdat',
  290. 'ic_wallet': '1378FC64-2907DE97-F980D21F[192,1].mmincog',
  291. 'ic_wallet_hex': '1378FC64-4DCB5174-872806A7[192,1].mmincox',
  292. 'hic_wallet': '1378FC64-B55E9958-77256FC1[192,1].incog.offset123',
  293. 'hic_wallet_old': '1378FC64-B55E9958-D85FF20C[192,1].incog-old.offset123',
  294. 'tmpdir': os.path.join('test','tmp7'),
  295. 'kapasswd': '',
  296. 'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses
  297. 'pass_idx_list': '1,4,9-11,1100',
  298. 'dep_generators': {
  299. 'mmdat': 'refwalletgen2',
  300. pwfile: 'refwalletgen2',
  301. 'addrs': 'refaddrgen2',
  302. 'akeys.mmenc': 'refkeyaddrgen2'
  303. },
  304. 'segwit': get_segwit_val()
  305. },
  306. '8': {
  307. 'name': 'reference wallet check (256-bit)',
  308. 'seed_len': 256,
  309. 'seed_id': '98831F3A',
  310. 'ref_bw_seed_id': 'B48CD7FC',
  311. 'addrfile_chk': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E','06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14'),
  312. 'keyaddrfile_chk': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2','A447 12C2 DD14 5A9B','0690 460D A600 D315'),
  313. 'passfile_chk': '2D6D 8FBA 422E 1315',
  314. 'passfile32_chk': 'F6C1 CDFB 97D9 FCAE',
  315. 'wpasswd': 'reference password',
  316. 'ref_wallet': '98831F3A-{}[256,1].mmdat'.format(('27F2BF93','E2687906')[g.testnet]),
  317. 'ref_addrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
  318. 'ref_segwitaddrfile':'98831F3A-S[1,31-33,500-501,1010-1011]{}.addrs'.format(tn_desc),
  319. 'ref_keyaddrfile': '98831F3A[1,31-33,500-501,1010-1011]{}.akeys.mmenc'.format(tn_desc),
  320. 'ref_passwdfile': '98831F3A-фубар@crypto.org-b58-20[1,4,9-11,1100].pws',
  321. 'ref_addrfile_chksum': ('6FEF 6FB9 7B13 5D91','3C2C 8558 BB54 079E')[g.testnet],
  322. 'ref_segwitaddrfile_chksum':('06C1 9C87 F25C 4EE6','58D1 7B6C E9F9 9C14')[g.testnet],
  323. 'ref_keyaddrfile_chksum': ('9F2D D781 1812 8BAD','7410 8F95 4B33 B4B2')[g.testnet],
  324. 'ref_passwdfile_chksum': 'A983 DAB9 5514 27FB',
  325. # 'ref_fake_unspent_data':'98831F3A_unspent.json',
  326. 'ref_tx_file': 'FFB367[1.234]{}.rawtx'.format(tn_desc),
  327. 'ic_wallet': '98831F3A-5482381C-18460FB1[256,1].mmincog',
  328. 'ic_wallet_hex': '98831F3A-1630A9F2-870376A9[256,1].mmincox',
  329. 'hic_wallet': '98831F3A-F59B07A0-559CEF19[256,1].incog.offset123',
  330. 'hic_wallet_old': '98831F3A-F59B07A0-848535F3[256,1].incog-old.offset123',
  331. 'tmpdir': os.path.join('test','tmp8'),
  332. 'kapasswd': '',
  333. 'addr_idx_list': '1010,500-501,31-33,1,33,500,1011', # 8 addresses
  334. 'pass_idx_list': '1,4,9-11,1100',
  335. 'dep_generators': {
  336. 'mmdat': 'refwalletgen3',
  337. pwfile: 'refwalletgen3',
  338. 'addrs': 'refaddrgen3',
  339. 'akeys.mmenc': 'refkeyaddrgen3'
  340. },
  341. 'segwit': get_segwit_val()
  342. },
  343. '9': {
  344. 'tmpdir': os.path.join('test','tmp9'),
  345. 'tool_enc_infn': 'tool_encrypt.in',
  346. # 'tool_enc_ref_infn': 'tool_encrypt_ref.in',
  347. 'wpasswd': 'reference password',
  348. 'dep_generators': {
  349. 'tool_encrypt.in': 'tool_encrypt',
  350. 'tool_encrypt.in.mmenc': 'tool_encrypt',
  351. # 'tool_encrypt_ref.in': 'tool_encrypt_ref',
  352. # 'tool_encrypt_ref.in.mmenc': 'tool_encrypt_ref',
  353. },
  354. },
  355. }
  356. from copy import deepcopy
  357. for a,b in ('6','11'),('7','12'),('8','13'):
  358. cfgs[b] = deepcopy(cfgs[a])
  359. cfgs[b]['tmpdir'] = os.path.join('test','tmp'+b)
  360. from collections import OrderedDict
  361. cmd_group = OrderedDict()
  362. cmd_group['help'] = OrderedDict([
  363. # test description depends
  364. ['helpscreens', (1,'help screens', [],1)],
  365. ['longhelpscreens', (1,'help screens (--longhelp)',[],1)],
  366. ])
  367. cmd_group['dfl_wallet'] = OrderedDict([
  368. ['walletgen_dfl_wallet', (15,'wallet generation (default wallet)',[[[],15]],1)],
  369. ['export_seed_dfl_wallet',(15,'seed export to mmseed format (default wallet)',[[[pwfile],15]],1)],
  370. ['addrgen_dfl_wallet',(15,'address generation (default wallet)',[[[pwfile],15]],1)],
  371. ['txcreate_dfl_wallet',(15,'transaction creation (default wallet)',[[['addrs'],15]],1)],
  372. ['txsign_dfl_wallet',(15,'transaction signing (default wallet)',[[['rawtx',pwfile],15]],1)],
  373. ['passchg_dfl_wallet',(16,'password, label and hash preset change (default wallet)',[[[pwfile],15]],1)],
  374. ['walletchk_newpass_dfl_wallet',(16,'wallet check with new pw, label and hash preset',[[[pwfile],16]],1)],
  375. ['delete_dfl_wallet',(15,'delete default wallet',[[[pwfile],15]],1)],
  376. ])
  377. cmd_group['main'] = OrderedDict([
  378. ['walletgen', (1,'wallet generation', [[['del_dw_run'],15]],1)],
  379. # ['walletchk', (1,'wallet check', [[['mmdat'],1]])],
  380. ['passchg', (5,'password, label and hash preset change',[[['mmdat',pwfile],1]],1)],
  381. ['walletchk_newpass',(5,'wallet check with new pw, label and hash preset',[[['mmdat',pwfile],5]],1)],
  382. ['addrgen', (1,'address generation', [[['mmdat',pwfile],1]],1)],
  383. ['addrimport', (1,'address import', [[['addrs'],1]],1)],
  384. ['txcreate', (1,'transaction creation', [[['addrs'],1]],1)],
  385. ['txbump', (1,'transaction fee bumping (no send)',[[['rawtx'],1]],1)],
  386. ['txsign', (1,'transaction signing', [[['mmdat','rawtx',pwfile,'txbump'],1]],1)],
  387. ['txsend', (1,'transaction sending', [[['sigtx'],1]])],
  388. # txdo must go after txsign
  389. ['txdo', (1,'online transaction', [[['sigtx','mmdat'],1]])],
  390. ['export_hex', (1,'seed export to hexadecimal format', [[['mmdat'],1]])],
  391. ['export_seed', (1,'seed export to mmseed format', [[['mmdat'],1]])],
  392. ['export_mnemonic', (1,'seed export to mmwords format', [[['mmdat'],1]])],
  393. ['export_incog', (1,'seed export to mmincog format', [[['mmdat'],1]])],
  394. ['export_incog_hex',(1,'seed export to mmincog hex format', [[['mmdat'],1]])],
  395. ['export_incog_hidden',(1,'seed export to hidden mmincog format', [[['mmdat'],1]])],
  396. ['addrgen_hex', (1,'address generation from mmhex file', [[['mmhex','addrs'],1]])],
  397. ['addrgen_seed', (1,'address generation from mmseed file', [[['mmseed','addrs'],1]])],
  398. ['addrgen_mnemonic',(1,'address generation from mmwords file',[[['mmwords','addrs'],1]])],
  399. ['addrgen_incog', (1,'address generation from mmincog file',[[['mmincog','addrs'],1]])],
  400. ['addrgen_incog_hex',(1,'address generation from mmincog hex file',[[['mmincox','addrs'],1]])],
  401. ['addrgen_incog_hidden',(1,'address generation from hidden mmincog file', [[[hincog_fn,'addrs'],1]])],
  402. ['keyaddrgen', (1,'key-address file generation', [[['mmdat',pwfile],1]])],
  403. ['txsign_keyaddr',(1,'transaction signing with key-address file', [[['akeys.mmenc','rawtx'],1]])],
  404. ['walletgen2',(2,'wallet generation (2), 128-bit seed', [[['del_dw_run'],15]])],
  405. ['addrgen2', (2,'address generation (2)', [[['mmdat'],2]])],
  406. ['txcreate2', (2,'transaction creation (2)', [[['addrs'],2]])],
  407. ['txsign2', (2,'transaction signing, two transactions',[[['mmdat','rawtx'],1],[['mmdat','rawtx'],2]])],
  408. ['export_mnemonic2', (2,'seed export to mmwords format (2)',[[['mmdat'],2]])],
  409. ['walletgen3',(3,'wallet generation (3)', [[['del_dw_run'],15]])],
  410. ['addrgen3', (3,'address generation (3)', [[['mmdat'],3]])],
  411. ['txcreate3', (3,'tx creation with inputs and outputs from two wallets', [[['addrs'],1],[['addrs'],3]])],
  412. ['txsign3', (3,'tx signing with inputs and outputs from two wallets',[[['mmdat'],1],[['mmdat','rawtx'],3]])],
  413. ['walletgen14', (14,'wallet generation (14)', [[['del_dw_run'],15]],14)],
  414. ['addrgen14', (14,'address generation (14)', [[['mmdat'],14]])],
  415. ['keyaddrgen14',(14,'key-address file generation (14)', [[['mmdat'],14]],14)],
  416. ['walletgen4',(4,'wallet generation (4) (brainwallet)', [[['del_dw_run'],15]])],
  417. ['addrgen4', (4,'address generation (4)', [[['mmdat'],4]])],
  418. ['txcreate4', (4,'tx creation with inputs and outputs from four seed sources, key-address file and non-MMGen inputs and outputs', [[['addrs'],1],[['addrs'],2],[['addrs'],3],[['addrs'],4],[['addrs','akeys.mmenc'],14]])],
  419. ['txsign4', (4,'tx signing with inputs and outputs from incog file, mnemonic file, wallet, brainwallet, key-address file and non-MMGen inputs and outputs', [[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','rawtx'],4],[['akeys.mmenc'],14]])],
  420. ['txdo4', (4,'tx creation,signing and sending with inputs and outputs from four seed sources, key-address file and non-MMGen inputs and outputs', [[['addrs'],1],[['addrs'],2],[['addrs'],3],[['addrs'],4],[['addrs','akeys.mmenc'],14],[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['mmbrain','rawtx'],4],[['akeys.mmenc'],14]])], # must go after txsign4
  421. ['txbump4', (4,'tx fee bump + send with inputs and outputs from four seed sources, key-address file and non-MMGen inputs and outputs', [[['akeys.mmenc'],14],[['mmincog'],1],[['mmwords'],2],[['mmdat'],3],[['akeys.mmenc'],14],[['mmbrain','sigtx','mmdat','txdo'],4]])], # must go after txsign4
  422. ])
  423. cmd_group['tool'] = OrderedDict([
  424. ['tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [],1)],
  425. ['tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[cfgs['9']['tool_enc_infn'],cfgs['9']['tool_enc_infn']+'.mmenc'],9]],1)],
  426. # ['tool_encrypt_ref', (9,"'mmgen-tool encrypt' (reference text)", [])],
  427. ['tool_find_incog_data', (9,"'mmgen-tool find_incog_data'", [[[hincog_fn],1],[[incog_id_fn],1]])],
  428. # ['pywallet', (9,"'mmgen-pywallet'", [],1)],
  429. ])
  430. # saved reference data
  431. cmd_group['ref'] = (
  432. # reading
  433. ('ref_wallet_chk', ([],'saved reference wallet')),
  434. ('ref_seed_chk', ([],'saved seed file')),
  435. ('ref_hex_chk', ([],'saved mmhex file')),
  436. ('ref_mn_chk', ([],'saved mnemonic file')),
  437. ('ref_hincog_chk', ([],'saved hidden incog reference wallet')),
  438. ('ref_brain_chk', ([],'saved brainwallet')),
  439. # generating new reference ('abc' brainwallet) files:
  440. ('refwalletgen', ([],'gen new refwallet')),
  441. ('refaddrgen', (['mmdat',pwfile],'new refwallet addr chksum')),
  442. ('refkeyaddrgen', (['mmdat',pwfile],'new refwallet key-addr chksum')),
  443. ('refpasswdgen', (['mmdat',pwfile],'new refwallet passwd file chksum')),
  444. ('ref_b32passwdgen',(['mmdat',pwfile],'new refwallet passwd file chksum (base32)')),
  445. )
  446. # misc. saved reference data
  447. cmd_group['ref_other'] = (
  448. ('ref_addrfile_chk', 'saved reference address file'),
  449. ('ref_segwitaddrfile_chk','saved reference address file (segwit)'),
  450. ('ref_keyaddrfile_chk','saved reference key-address file'),
  451. ('ref_passwdfile_chk', 'saved reference password file'),
  452. # Create the fake inputs:
  453. # ('txcreate8', 'transaction creation (8)'),
  454. ('ref_tx_chk', 'saved reference tx file'),
  455. ('ref_brain_chk_spc3', 'saved brainwallet (non-standard spacing)'),
  456. ('ref_tool_decrypt', 'decryption of saved MMGen-encrypted file'),
  457. )
  458. # mmgen-walletconv:
  459. cmd_group['conv_in'] = ( # reading
  460. ('ref_wallet_conv', 'conversion of saved reference wallet'),
  461. ('ref_mn_conv', 'conversion of saved mnemonic'),
  462. ('ref_seed_conv', 'conversion of saved seed file'),
  463. ('ref_hex_conv', 'conversion of saved hexadecimal seed file'),
  464. ('ref_brain_conv', 'conversion of ref brainwallet'),
  465. ('ref_incog_conv', 'conversion of saved incog wallet'),
  466. ('ref_incox_conv', 'conversion of saved hex incog wallet'),
  467. ('ref_hincog_conv', 'conversion of saved hidden incog wallet'),
  468. ('ref_hincog_conv_old','conversion of saved hidden incog wallet (old format)')
  469. )
  470. cmd_group['conv_out'] = ( # writing
  471. ('ref_wallet_conv_out', 'ref seed conversion to wallet'),
  472. ('ref_mn_conv_out', 'ref seed conversion to mnemonic'),
  473. ('ref_hex_conv_out', 'ref seed conversion to hex seed'),
  474. ('ref_seed_conv_out', 'ref seed conversion to seed'),
  475. ('ref_incog_conv_out', 'ref seed conversion to incog data'),
  476. ('ref_incox_conv_out', 'ref seed conversion to hex incog data'),
  477. ('ref_hincog_conv_out', 'ref seed conversion to hidden incog data')
  478. )
  479. cmd_list = OrderedDict()
  480. for k in cmd_group: cmd_list[k] = []
  481. cmd_data = OrderedDict()
  482. for k,v in (
  483. ('help', ('help screens',[])),
  484. ('dfl_wallet', ('basic operations with default wallet',[15,16])),
  485. ('main', ('basic operations',[1,2,3,4,5,15,16])),
  486. ('tool', ('tools',[9]))
  487. ):
  488. cmd_data['info_'+k] = v
  489. for i in cmd_group[k]:
  490. cmd_list[k].append(i)
  491. cmd_data[i] = cmd_group[k][i]
  492. cmd_data['info_ref'] = 'reference data',[6,7,8]
  493. for a,b in cmd_group['ref']:
  494. for i,j in (1,128),(2,192),(3,256):
  495. k = a+str(i)
  496. cmd_list['ref'].append(k)
  497. cmd_data[k] = (5+i,'%s (%s-bit)' % (b[1],j),[[b[0],5+i]],1)
  498. cmd_data['info_ref_other'] = 'other reference data',[8]
  499. for a,b in cmd_group['ref_other']:
  500. cmd_list['ref_other'].append(a)
  501. cmd_data[a] = (8,b,[[[],8]],1)
  502. cmd_data['info_conv_in'] = 'wallet conversion from reference data',[11,12,13]
  503. for a,b in cmd_group['conv_in']:
  504. for i,j in (1,128),(2,192),(3,256):
  505. k = a+str(i)
  506. cmd_list['conv_in'].append(k)
  507. cmd_data[k] = (10+i,'%s (%s-bit)' % (b,j),[[[],10+i]],1)
  508. cmd_data['info_conv_out'] = 'wallet conversion to reference data',[11,12,13]
  509. for a,b in cmd_group['conv_out']:
  510. for i,j in (1,128),(2,192),(3,256):
  511. k = a+str(i)
  512. cmd_list['conv_out'].append(k)
  513. cmd_data[k] = (10+i,'%s (%s-bit)' % (b,j),[[[],10+i]],1)
  514. utils = {
  515. 'check_deps': 'check dependencies for specified command',
  516. 'clean': 'clean specified tmp dir(s) 1,2,3,4,5 or 6 (no arg = all dirs)',
  517. }
  518. addrs_per_wallet = 8
  519. # total of two outputs must be < 10 BTC
  520. for k in cfgs:
  521. cfgs[k]['amts'] = [0,0]
  522. for idx,mod in (0,6),(1,4):
  523. cfgs[k]['amts'][idx] = '%s.%s' % ((getrandnum(2) % mod), str(getrandnum(4))[:5])
  524. meta_cmds = OrderedDict([
  525. ['ref1', ('refwalletgen1','refaddrgen1','refkeyaddrgen1')],
  526. ['ref2', ('refwalletgen2','refaddrgen2','refkeyaddrgen2')],
  527. ['ref3', ('refwalletgen3','refaddrgen3','refkeyaddrgen3')],
  528. ['gen', ('walletgen','addrgen')],
  529. ['pass', ('passchg','walletchk_newpass')],
  530. ['tx', ('addrimport','txcreate','txsign','txsend')],
  531. ['export', [k for k in cmd_data if k[:7] == 'export_' and cmd_data[k][0] == 1]],
  532. ['gen_sp', [k for k in cmd_data if k[:8] == 'addrgen_' and cmd_data[k][0] == 1]],
  533. ['online', ('keyaddrgen','txsign_keyaddr')],
  534. ['2', [k for k in cmd_data if cmd_data[k][0] == 2]],
  535. ['3', [k for k in cmd_data if cmd_data[k][0] == 3]],
  536. ['4', [k for k in cmd_data if cmd_data[k][0] == 4]],
  537. ['saved_ref1', [c[0]+'1' for c in cmd_group['ref']]],
  538. ['saved_ref2', [c[0]+'2' for c in cmd_group['ref']]],
  539. ['saved_ref3', [c[0]+'3' for c in cmd_group['ref']]],
  540. ['saved_ref_other', [c[0] for c in cmd_group['ref_other']]],
  541. ['saved_ref_conv_in1', [c[0]+'1' for c in cmd_group['conv_in']]],
  542. ['saved_ref_conv_in2', [c[0]+'2' for c in cmd_group['conv_in']]],
  543. ['saved_ref_conv_in3', [c[0]+'3' for c in cmd_group['conv_in']]],
  544. ['saved_ref_conv_out1', [c[0]+'1' for c in cmd_group['conv_out']]],
  545. ['saved_ref_conv_out2', [c[0]+'2' for c in cmd_group['conv_out']]],
  546. ['saved_ref_conv_out3', [c[0]+'3' for c in cmd_group['conv_out']]],
  547. ])
  548. del cmd_group
  549. add_spawn_args = ' '.join(['{} {}'.format(
  550. '--'+k.replace('_','-'),
  551. getattr(opt,k) if getattr(opt,k) != True else ''
  552. ) for k in 'testnet','rpc_host','rpc_port','regtest' if getattr(opt,k)]).split()
  553. add_spawn_args += ['--data-dir',data_dir]
  554. if opt.profile: opt.names = True
  555. if opt.resume: opt.skip_deps = True
  556. if opt.log:
  557. log_fd = open(log_file,'a')
  558. log_fd.write('\nLog started: %s\n' % make_timestr())
  559. usr_rand_chars = (5,30)[bool(opt.usr_random)]
  560. usr_rand_arg = '-r%s' % usr_rand_chars
  561. cmd_total = 0
  562. if opt.system: sys.path.pop(0)
  563. ia = bool(opt.interactive)
  564. # Disable color in spawned scripts so we can parse their output
  565. os.environ['MMGEN_DISABLE_COLOR'] = '1'
  566. os.environ['MMGEN_NO_LICENSE'] = '1'
  567. os.environ['MMGEN_MIN_URANDCHARS'] = '3'
  568. os.environ['MMGEN_BOGUS_SEND'] = '1'
  569. def get_segwit_arg(cfg): return ([],['--type','segwit'])[cfg['segwit']]
  570. # Tell spawned programs they're running in the test suite
  571. os.environ['MMGEN_TEST_SUITE'] = '1'
  572. if opt.debug_scripts: os.environ['MMGEN_DEBUG'] = '1'
  573. if opt.buf_keypress:
  574. send_delay = 0.3
  575. else:
  576. send_delay = 0
  577. os.environ['MMGEN_DISABLE_HOLD_PROTECT'] = '1'
  578. if opt.exact_output:
  579. def msg(s): pass
  580. vmsg = vmsg_r = msg_r = msg
  581. else:
  582. def msg(s): sys.stderr.write(s+'\n')
  583. def vmsg(s):
  584. if opt.verbose: sys.stderr.write(s+'\n')
  585. def msg_r(s): sys.stderr.write(s)
  586. def vmsg_r(s):
  587. if opt.verbose: sys.stderr.write(s)
  588. stderr_save = sys.stderr
  589. def silence():
  590. if not (opt.verbose or opt.exact_output):
  591. f = ('/dev/null','stderr.out')[g.platform=='win']
  592. sys.stderr = open(f,'a')
  593. def end_silence():
  594. if not (opt.verbose or opt.exact_output):
  595. sys.stderr = stderr_save
  596. def errmsg(s): stderr_save.write(s+'\n')
  597. def errmsg_r(s): stderr_save.write(s)
  598. if opt.list_cmds:
  599. fs = ' {:<{w}} - {}'
  600. Msg(green('AVAILABLE COMMANDS:'))
  601. w = max([len(i) for i in cmd_data])
  602. for cmd in cmd_data:
  603. if cmd[:5] == 'info_':
  604. m = capfirst(cmd_data[cmd][0])
  605. Msg(green(' %s:' % m))
  606. continue
  607. Msg(' '+fs.format(cmd,cmd_data[cmd][1],w=w))
  608. w = max([len(i) for i in meta_cmds])
  609. Msg(green('\nAVAILABLE METACOMMANDS:'))
  610. for cmd in meta_cmds:
  611. Msg(fs.format(cmd,' '.join(meta_cmds[cmd]),w=w))
  612. w = max([len(i) for i in cmd_list])
  613. Msg(green('\nAVAILABLE COMMAND GROUPS:'))
  614. for g in cmd_list:
  615. Msg(fs.format(g,' '.join(cmd_list[g]),w=w))
  616. Msg(green('\nAVAILABLE UTILITIES:'))
  617. w = max([len(i) for i in utils])
  618. for cmd in sorted(utils):
  619. Msg(fs.format(cmd,utils[cmd],w=w))
  620. sys.exit(0)
  621. import time,re
  622. if g.platform == 'linux':
  623. import pexpect
  624. if opt.popen_spawn:
  625. import termios,atexit
  626. def at_exit(): os.system('stty sane')
  627. atexit.register(at_exit)
  628. from pexpect.popen_spawn import PopenSpawn
  629. use_popen_spawn,NL = True,'\n'
  630. else:
  631. use_popen_spawn,NL = False,'\r\n'
  632. else: # Windows
  633. use_popen_spawn,NL = True,'\r\n'
  634. try:
  635. import pexpect
  636. from pexpect.popen_spawn import PopenSpawn
  637. except:
  638. ia = True
  639. m1 = ('Missing pexpect module detected. Skipping some tests and running in'
  640. '\ninteractive mode. User prompts and control value checks will be ')
  641. m2 = 'HIGHLIGHTED IN GREEN'
  642. m3 = '.\nControl values should be checked against the program output.\nContinue?'
  643. if not keypress_confirm(green(m1)+grnbg(m2)+green(m3),default_yes=True):
  644. errmsg('Exiting at user request')
  645. sys.exit(0)
  646. def my_send(p,t,delay=send_delay,s=False):
  647. if delay: time.sleep(delay)
  648. ret = p.send(t) # returns num bytes written
  649. if delay: time.sleep(delay)
  650. if opt.verbose:
  651. ls = (' ','')[bool(opt.debug or not s)]
  652. es = (' ','')[bool(s)]
  653. msg('%sSEND %s%s' % (ls,es,yellow("'%s'"%t.replace('\n',r'\n'))))
  654. return ret
  655. def my_expect(p,s,t='',delay=send_delay,regex=False,nonl=False):
  656. quo = ('',"'")[type(s) == str]
  657. if opt.verbose: msg_r('EXPECT %s' % yellow(quo+str(s)+quo))
  658. else: msg_r('+')
  659. try:
  660. if s == '': ret = 0
  661. else:
  662. f = (p.expect_exact,p.expect)[bool(regex)]
  663. ret = f(s,timeout=(60,5)[bool(opt.debug_pexpect)])
  664. except pexpect.TIMEOUT:
  665. if opt.debug_pexpect: raise
  666. errmsg(red('\nERROR. Expect %s%s%s timed out. Exiting' % (quo,s,quo)))
  667. sys.exit(1)
  668. debug_pexpect_msg(p)
  669. if opt.debug or (opt.verbose and type(s) != str): msg_r(' ==> %s ' % ret)
  670. if ret == -1:
  671. errmsg('Error. Expect returned %s' % ret)
  672. sys.exit(1)
  673. else:
  674. if t == '':
  675. if not nonl: vmsg('')
  676. else:
  677. my_send(p,t,delay,s)
  678. return ret
  679. def get_file_with_ext(ext,mydir,delete=True,no_dot=False):
  680. dot = ('.','')[bool(no_dot)]
  681. flist = [os.path.join(mydir,f) for f in os.listdir(mydir)
  682. if f == ext or f[-len(dot+ext):] == dot+ext]
  683. if not flist: return False
  684. if len(flist) > 1:
  685. if delete:
  686. if not opt.quiet:
  687. msg("Multiple *.{} files in '{}' - deleting".format(ext,mydir))
  688. for f in flist:
  689. msg(f)
  690. os.unlink(f)
  691. return False
  692. else:
  693. return flist[0]
  694. def find_generated_exts(cmd):
  695. out = []
  696. for k in cfgs:
  697. for ext,prog in cfgs[k]['dep_generators'].items():
  698. if prog == cmd:
  699. out.append((ext,cfgs[k]['tmpdir']))
  700. return out
  701. def get_addrfile_checksum(display=False):
  702. addrfile = get_file_with_ext('addrs',cfg['tmpdir'])
  703. silence()
  704. from mmgen.addr import AddrList
  705. chk = AddrList(addrfile).chksum
  706. if opt.verbose and display: msg('Checksum: %s' % cyan(chk))
  707. end_silence()
  708. return chk
  709. def verify_checksum_or_exit(checksum,chk):
  710. if checksum != chk:
  711. errmsg(red('Checksum error: %s' % chk))
  712. sys.exit(1)
  713. vmsg(green('Checksums match: %s') % (cyan(chk)))
  714. def debug_pexpect_msg(p):
  715. if opt.debug_pexpect:
  716. errmsg('\n{}{}{}'.format(red('BEFORE ['),p.before,red(']')))
  717. errmsg('{}{}{}'.format(red('MATCH ['),p.after,red(']')))
  718. class MMGenExpect(object):
  719. def __init__(self,name,mmgen_cmd,cmd_args=[],extra_desc='',no_output=False):
  720. cmd = (('./','')[bool(opt.system)]+mmgen_cmd,'python')[g.platform=='win']
  721. cmd_args = add_spawn_args + cmd_args
  722. args = (cmd_args,[mmgen_cmd]+cmd_args)[g.platform=='win']
  723. desc = (cmd_data[name][1],name)[bool(opt.names)] + (' ' + extra_desc).strip()
  724. for i in args:
  725. if type(i) not in (str,unicode):
  726. m1 = 'Error: missing input files in cmd line?:'
  727. m2 = '\nName: {}\nCmd: {}\nCmd args: {}'
  728. die(2,(m1+m2).format(name,cmd,args))
  729. if use_popen_spawn:
  730. args = [("'"+a+"'" if ' ' in a else a) for a in args]
  731. cmd_str = '{} {}'.format(cmd,' '.join(args))
  732. if use_popen_spawn:
  733. cmd_str = cmd_str.replace('\\','/')
  734. if opt.log:
  735. log_fd.write(cmd_str+'\n')
  736. if opt.verbose or opt.print_cmdline or opt.exact_output:
  737. clr1,clr2,eol = ((green,cyan,'\n'),(nocolor,nocolor,' '))[bool(opt.print_cmdline)]
  738. sys.stderr.write(green('Testing: {}\n'.format(desc)))
  739. sys.stderr.write(clr1('Executing {}{}'.format(clr2(cmd_str),eol)))
  740. else:
  741. m = 'Testing %s: ' % desc
  742. msg_r((m,yellow(m))[ia])
  743. if mmgen_cmd == '': return
  744. if opt.direct_exec or ia:
  745. msg('')
  746. from subprocess import call,check_output
  747. f = (call,check_output)[bool(no_output)]
  748. ret = f([cmd] + args)
  749. if f == call and ret != 0:
  750. m = 'ERROR: process returned a non-zero exit status (%s)'
  751. die(1,red(m % ret))
  752. else:
  753. if opt.traceback:
  754. cmd,args = tb_cmd,[cmd]+args
  755. cmd_str = tb_cmd + ' ' + cmd_str
  756. if use_popen_spawn:
  757. self.p = PopenSpawn(cmd_str)
  758. else:
  759. self.p = pexpect.spawn(cmd,args)
  760. if opt.exact_output: self.p.logfile = sys.stdout
  761. def ok(self,exit_val=0):
  762. ret = self.p.wait()
  763. if ret != exit_val:
  764. die(1,red('test.py: spawned program exited with value {}'.format(ret)))
  765. if opt.profile: return
  766. if opt.verbose or opt.exact_output:
  767. sys.stderr.write(green('OK\n'))
  768. else: msg(' OK')
  769. def cmp_or_die(self,s,t,skip_ok=False,exit_val=0):
  770. ret = self.p.wait()
  771. if ret != exit_val:
  772. die(1,red('test.py: spawned program exited with value {}'.format(ret)))
  773. if s == t:
  774. if not skip_ok: ok()
  775. else:
  776. sys.stderr.write(red(
  777. 'ERROR: recoded data:\n%s\ndiffers from original data:\n%s\n' %
  778. (repr(t),repr(s))))
  779. sys.exit(3)
  780. def license(self):
  781. if 'MMGEN_NO_LICENSE' in os.environ: return
  782. p = "'w' for conditions and warranty info, or 'c' to continue: "
  783. my_expect(self.p,p,'c')
  784. def label(self,label='Test Label'):
  785. p = 'Enter a wallet label, or hit ENTER for no label: '
  786. my_expect(self.p,p,label+'\n')
  787. def usr_rand_out(self,saved=False):
  788. m = '%suser-supplied entropy' % (('','saved ')[saved])
  789. my_expect(self.p,'Generating encryption key from OS random data plus ' + m)
  790. def usr_rand(self,num_chars):
  791. if opt.usr_random:
  792. self.interactive()
  793. my_send(self.p,'\n')
  794. else:
  795. rand_chars = list(getrandstr(num_chars,no_space=True))
  796. my_expect(self.p,'symbols left: ','x')
  797. try:
  798. vmsg_r('SEND ')
  799. while self.p.expect('left: ',0.1) == 0:
  800. ch = rand_chars.pop(0)
  801. msg_r(yellow(ch)+' ' if opt.verbose else '+')
  802. self.p.send(ch)
  803. except:
  804. vmsg('EOT')
  805. my_expect(self.p,'ENTER to continue: ','\n')
  806. def passphrase_new(self,desc,passphrase):
  807. my_expect(self.p,('Enter passphrase for %s: ' % desc), passphrase+'\n')
  808. my_expect(self.p,'Repeat passphrase: ', passphrase+'\n')
  809. def passphrase(self,desc,passphrase,pwtype=''):
  810. if pwtype: pwtype += ' '
  811. my_expect(self.p,('Enter %spassphrase for %s.*?: ' % (pwtype,desc)),
  812. passphrase+'\n',regex=True)
  813. def hash_preset(self,desc,preset=''):
  814. my_expect(self.p,('Enter hash preset for %s' % desc))
  815. my_expect(self.p,('or hit ENTER .*?:'), str(preset)+'\n',regex=True)
  816. def written_to_file(self,desc,overwrite_unlikely=False,query='Overwrite? ',oo=False):
  817. s1 = '%s written to file ' % desc
  818. s2 = query + "Type uppercase 'YES' to confirm: "
  819. ret = my_expect(self.p,([s1,s2],s1)[overwrite_unlikely])
  820. if ret == 1:
  821. my_send(self.p,'YES\n')
  822. # if oo:
  823. outfile = self.expect_getend("Overwriting file '").rstrip("'")
  824. return outfile
  825. # else:
  826. # ret = my_expect(self.p,s1)
  827. self.expect(NL,nonl=True)
  828. outfile = self.p.before.strip().strip("'")
  829. if opt.debug_pexpect: msgred('Outfile [%s]' % outfile)
  830. vmsg('%s file: %s' % (desc,cyan(outfile.replace("'",''))))
  831. return outfile
  832. def no_overwrite(self):
  833. self.expect("Overwrite? Type uppercase 'YES' to confirm: ",'\n')
  834. self.expect('Exiting at user request')
  835. def tx_view(self):
  836. my_expect(self.p,r'View .*?transaction.*? \(y\)es, \(N\)o, pager \(v\)iew.*?: ','\n',regex=True)
  837. def expect_getend(self,s,regex=False):
  838. ret = self.expect(s,regex=regex,nonl=True)
  839. debug_pexpect_msg(self.p)
  840. # end = self.readline().strip()
  841. # readline() of partial lines doesn't work with PopenSpawn, so do this instead:
  842. self.expect(NL,nonl=True)
  843. debug_pexpect_msg(self.p)
  844. end = self.p.before
  845. vmsg(' ==> %s' % cyan(end))
  846. return end
  847. def interactive(self):
  848. return self.p.interact()
  849. def logfile(self,arg):
  850. self.p.logfile = arg
  851. def expect(self,*args,**kwargs):
  852. return my_expect(self.p,*args,**kwargs)
  853. def send(self,*args,**kwargs):
  854. return my_send(self.p,*args,**kwargs)
  855. # def readline(self):
  856. # return self.p.readline()
  857. # def readlines(self):
  858. # return [l.rstrip()+'\n' for l in self.p.readlines()]
  859. def read(self,n=None):
  860. return self.p.read(n)
  861. def close(self):
  862. if not use_popen_spawn:
  863. self.p.close()
  864. from mmgen.obj import BTCAmt
  865. from mmgen.bitcoin import verify_addr
  866. def create_fake_unspent_entry(btcaddr,al_id=None,idx=None,lbl=None,non_mmgen=False,segwit=False):
  867. if lbl: lbl = ' ' + lbl
  868. spk1,spk2 = (('76a914','88ac'),('a914','87'))[segwit and btcaddr.addr_fmt=='p2sh']
  869. return {
  870. 'account': 'btc:{}'.format(btcaddr) if non_mmgen else (u'{}:{}{}'.format(al_id,idx,lbl.decode('utf8'))),
  871. 'vout': int(getrandnum(4) % 8),
  872. 'txid': hexlify(os.urandom(32)).decode('utf8'),
  873. 'amount': BTCAmt('%s.%s' % (10+(getrandnum(4) % 40), getrandnum(4) % 100000000)),
  874. 'address': btcaddr,
  875. 'spendable': False,
  876. 'scriptPubKey': (spk1+verify_addr(btcaddr,return_hex=True)+spk2),
  877. 'confirmations': getrandnum(4) % 50000
  878. }
  879. labels = [
  880. "Automotive",
  881. "Travel expenses",
  882. "Healthcare",
  883. "Freelancing 1",
  884. "Freelancing 2",
  885. "Alice's allowance",
  886. "Bob's bequest",
  887. "House purchase",
  888. "Real estate fund",
  889. "Job 1",
  890. "XYZ Corp.",
  891. "Eddie's endowment",
  892. "Emergency fund",
  893. "Real estate fund",
  894. "Ian's inheritance",
  895. "",
  896. "Rainy day",
  897. "Fred's funds",
  898. "Job 2",
  899. "Carl's capital",
  900. ]
  901. label_iter = None
  902. def create_fake_unspent_data(adata,tx_data,non_mmgen_input=''):
  903. out = []
  904. for d in tx_data.values():
  905. al = adata.addrlist(d['al_id'])
  906. for n,(idx,btcaddr) in enumerate(al.addrpairs()):
  907. while True:
  908. try: lbl = next(label_iter)
  909. except: label_iter = iter(labels)
  910. else: break
  911. out.append(create_fake_unspent_entry(btcaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
  912. if n == 0: # create a duplicate address. This means addrs_per_wallet += 1
  913. out.append(create_fake_unspent_entry(btcaddr,d['al_id'],idx,lbl,segwit=d['segwit']))
  914. if non_mmgen_input:
  915. privnum = getrandnum(32)
  916. from mmgen.bitcoin import privnum2addr,hex2wif
  917. from mmgen.obj import BTCAddr
  918. btcaddr = BTCAddr(privnum2addr(privnum,compressed=True))
  919. of = os.path.join(cfgs[non_mmgen_input]['tmpdir'],non_mmgen_fn)
  920. wif = hex2wif('{:064x}'.format(privnum),compressed=True)
  921. # Msg(yellow(wif + ' ' + btcaddr))
  922. write_data_to_file(of,wif+'\n','compressed bitcoin key',silent=True)
  923. out.append(create_fake_unspent_entry(btcaddr,non_mmgen=True,segwit=False))
  924. # msg('\n'.join([repr(o) for o in out])); sys.exit(0)
  925. return out
  926. def write_fake_data_to_file(d):
  927. unspent_data_file = os.path.join(cfg['tmpdir'],'unspent.json')
  928. write_data_to_file(unspent_data_file,d,'Unspent outputs',silent=True)
  929. os.environ['MMGEN_BOGUS_WALLET_DATA'] = unspent_data_file
  930. bwd_msg = 'MMGEN_BOGUS_WALLET_DATA=%s' % unspent_data_file
  931. if opt.print_cmdline: msg(bwd_msg)
  932. if opt.log: log_fd.write(bwd_msg + ' ')
  933. if opt.verbose or opt.exact_output:
  934. sys.stderr.write("Fake transaction wallet data written to file '%s'\n" % unspent_data_file)
  935. def create_tx_data(sources):
  936. from mmgen.addr import AddrList,AddrData,AddrIdxList
  937. tx_data,ad = {},AddrData()
  938. for s in sources:
  939. afile = get_file_with_ext('addrs',cfgs[s]['tmpdir'])
  940. al = AddrList(afile)
  941. ad.add(al)
  942. aix = AddrIdxList(fmt_str=cfgs[s]['addr_idx_list'])
  943. if len(aix) != addrs_per_wallet:
  944. errmsg(red('Address index list length != %s: %s' %
  945. (addrs_per_wallet,repr(aix))))
  946. sys.exit(0)
  947. tx_data[s] = {
  948. 'addrfile': afile,
  949. 'chk': al.chksum,
  950. 'al_id': al.al_id,
  951. 'addr_idxs': aix[-2:],
  952. 'segwit': cfgs[s]['segwit']
  953. }
  954. return ad,tx_data
  955. def make_txcreate_cmdline(tx_data):
  956. from mmgen.bitcoin import privnum2addr
  957. btcaddr = privnum2addr(getrandnum(32),compressed=True)
  958. cmd_args = ['-d',cfg['tmpdir']]
  959. for num in tx_data:
  960. s = tx_data[num]
  961. cmd_args += [
  962. '{}:{},{}'.format(s['al_id'],s['addr_idxs'][0],cfgs[num]['amts'][0]),
  963. ]
  964. # + one change address and one BTC address
  965. if num is tx_data.keys()[-1]:
  966. cmd_args += ['{}:{}'.format(s['al_id'],s['addr_idxs'][1])]
  967. cmd_args += ['{},{}'.format(btcaddr,cfgs[num]['amts'][1])]
  968. return cmd_args + [tx_data[num]['addrfile'] for num in tx_data]
  969. def add_comments_to_addr_file(addrfile,outfile):
  970. silence()
  971. msg(green("Adding comments to address file '%s'" % addrfile))
  972. from mmgen.addr import AddrList
  973. a = AddrList(addrfile)
  974. for n,idx in enumerate(a.idxs(),1):
  975. if n % 2: a.set_comment(idx,'Test address %s' % n)
  976. a.format(enable_comments=True)
  977. write_data_to_file(outfile,a.fmt_data,silent=True)
  978. end_silence()
  979. def make_brainwallet_file(fn):
  980. # Print random words with random whitespace in between
  981. from mmgen.mn_tirosh import words
  982. wl = words.split()
  983. nwords,ws_list,max_spaces = 10,' \n',5
  984. def rand_ws_seq():
  985. nchars = getrandnum(1) % max_spaces + 1
  986. return ''.join([ws_list[getrandnum(1)%len(ws_list)] for i in range(nchars)])
  987. rand_pairs = [wl[getrandnum(4) % len(wl)] + rand_ws_seq() for i in range(nwords)]
  988. d = ''.join(rand_pairs).rstrip() + '\n'
  989. if opt.verbose: msg_r('Brainwallet password:\n%s' % cyan(d))
  990. write_data_to_file(fn,d,'brainwallet password',silent=True)
  991. def do_between():
  992. if opt.pause:
  993. if keypress_confirm(green('Continue?'),default_yes=True):
  994. if opt.verbose or opt.exact_output: sys.stderr.write('\n')
  995. else:
  996. errmsg('Exiting at user request')
  997. sys.exit(0)
  998. elif opt.verbose or opt.exact_output:
  999. sys.stderr.write('\n')
  1000. rebuild_list = OrderedDict()
  1001. def check_needs_rerun(
  1002. ts,
  1003. cmd,
  1004. build=False,
  1005. root=True,
  1006. force_delete=False,
  1007. dpy=False
  1008. ):
  1009. rerun = (False,True)[root] # force_delete is not passed to recursive call
  1010. fns = []
  1011. if force_delete or not root:
  1012. # does cmd produce a needed dependency(ies)?
  1013. ret = ts.get_num_exts_for_cmd(cmd,dpy)
  1014. if ret:
  1015. for ext in ret[1]:
  1016. fn = get_file_with_ext(ext,cfgs[ret[0]]['tmpdir'],delete=build)
  1017. if fn:
  1018. if force_delete: os.unlink(fn)
  1019. else: fns.append(fn)
  1020. else: rerun = True
  1021. fdeps = ts.generate_file_deps(cmd)
  1022. cdeps = ts.generate_cmd_deps(fdeps)
  1023. # print 'cmd,fdeps,cdeps,fns: ',cmd,fdeps,cdeps,fns # DEBUG
  1024. for fn in fns:
  1025. my_age = os.stat(fn).st_mtime
  1026. for num,ext in fdeps:
  1027. f = get_file_with_ext(ext,cfgs[num]['tmpdir'],delete=build)
  1028. if f and os.stat(f).st_mtime > my_age:
  1029. rerun = True
  1030. for cdep in cdeps:
  1031. if check_needs_rerun(ts,cdep,build=build,root=False,dpy=cmd):
  1032. rerun = True
  1033. if build:
  1034. if rerun:
  1035. for fn in fns:
  1036. if not root: os.unlink(fn)
  1037. if not (dpy and opt.skip_deps):
  1038. ts.do_cmd(cmd)
  1039. if not root: do_between()
  1040. else:
  1041. # If prog produces multiple files:
  1042. if cmd not in rebuild_list or rerun == True:
  1043. rebuild_list[cmd] = (rerun,fns[0] if fns else '') # FIX
  1044. return rerun
  1045. def refcheck(desc,chk,refchk):
  1046. vmsg("Comparing %s '%s' to stored reference" % (desc,chk))
  1047. if chk == refchk:
  1048. ok()
  1049. else:
  1050. if not opt.verbose: errmsg('')
  1051. errmsg(red("""
  1052. Fatal error - %s '%s' does not match reference value '%s'. Aborting test
  1053. """.strip() % (desc,chk,refchk)))
  1054. sys.exit(3)
  1055. def check_deps(cmds):
  1056. if len(cmds) != 1:
  1057. die(1,'Usage: %s check_deps <command>' % g.prog_name)
  1058. cmd = cmds[0]
  1059. if cmd not in cmd_data:
  1060. die(1,"'%s': unrecognized command" % cmd)
  1061. if not opt.quiet:
  1062. msg("Checking dependencies for '%s'" % (cmd))
  1063. check_needs_rerun(ts,cmd,build=False)
  1064. w = max(len(i) for i in rebuild_list) + 1
  1065. for cmd in rebuild_list:
  1066. c = rebuild_list[cmd]
  1067. m = 'Rebuild' if (c[0] and c[1]) else 'Build' if c[0] else 'OK'
  1068. msg('cmd {:<{w}} {}'.format(cmd+':', m, w=w))
  1069. # mmsg(cmd,c)
  1070. def clean(usr_dirs=[]):
  1071. if opt.skip_deps and not ia: return
  1072. all_dirs = MMGenTestSuite().list_tmp_dirs()
  1073. dirs = (usr_dirs or all_dirs)
  1074. for d in sorted(dirs):
  1075. if str(d) in all_dirs:
  1076. cleandir(all_dirs[str(d)])
  1077. else:
  1078. die(1,'%s: invalid directory number' % d)
  1079. cleandir(os.path.join('test','data_dir'))
  1080. class MMGenTestSuite(object):
  1081. def __init__(self):
  1082. pass
  1083. def list_tmp_dirs(self):
  1084. d = {}
  1085. for k in cfgs: d[k] = cfgs[k]['tmpdir']
  1086. return d
  1087. def get_num_exts_for_cmd(self,cmd,dpy=False): # dpy ignored here
  1088. num = str(cmd_data[cmd][0])
  1089. dgl = cfgs[num]['dep_generators']
  1090. # mmsg(num,cmd,dgl)
  1091. if cmd in dgl.values():
  1092. exts = [k for k in dgl if dgl[k] == cmd]
  1093. return (num,exts)
  1094. else:
  1095. return None
  1096. def do_cmd(self,cmd):
  1097. if ia and (len(cmd_data[cmd]) < 4 or cmd_data[cmd][3] != 1): return
  1098. # delete files produced by this cmd
  1099. # for ext,tmpdir in find_generated_exts(cmd):
  1100. # print cmd, get_file_with_ext(ext,tmpdir)
  1101. d = [(str(num),ext) for exts,num in cmd_data[cmd][2] for ext in exts]
  1102. # delete files depended on by this cmd
  1103. al = [get_file_with_ext(ext,cfgs[num]['tmpdir']) for num,ext in d]
  1104. global cfg
  1105. cfg = cfgs[str(cmd_data[cmd][0])]
  1106. if opt.resume:
  1107. if cmd == opt.resume:
  1108. msg(yellow("Resuming at '%s'" % cmd))
  1109. opt.resume = False
  1110. opt.skip_deps = False
  1111. else:
  1112. return
  1113. if opt.profile: start = time.time()
  1114. self.__class__.__dict__[cmd](*([self,cmd] + al))
  1115. if opt.profile:
  1116. msg('\r\033[50C{:.4f}'.format(time.time() - start))
  1117. global cmd_total
  1118. cmd_total += 1
  1119. def generate_file_deps(self,cmd):
  1120. return [(str(n),e) for exts,n in cmd_data[cmd][2] for e in exts]
  1121. def generate_cmd_deps(self,fdeps):
  1122. return [cfgs[str(n)]['dep_generators'][ext] for n,ext in fdeps]
  1123. def helpscreens(self,name,arg='--help'):
  1124. for s in scripts:
  1125. t = MMGenExpect(name,('mmgen-'+s),[arg],extra_desc='(mmgen-%s)'%s,no_output=True)
  1126. if not ia:
  1127. t.read(); t.ok()
  1128. def longhelpscreens(self,name): self.helpscreens(name,arg='--longhelp')
  1129. def walletgen(self,name,del_dw_run='dummy',seed_len=None,gen_dfl_wallet=False):
  1130. if ia:
  1131. m = "\nAnswer '{}' at the the interactive prompt".format(('n','y')[gen_dfl_wallet])
  1132. msg(grnbg(m))
  1133. write_to_tmpfile(cfg,pwfile,cfg['wpasswd']+'\n')
  1134. add_args = ([usr_rand_arg],
  1135. ['-q','-r0','-L','Interactive Mode Wallet','-P',get_tmpfile_fn(cfg,pwfile)])[bool(ia)]
  1136. args = ['-d',cfg['tmpdir'],'-p1']
  1137. if seed_len: args += ['-l',str(seed_len)]
  1138. t = MMGenExpect(name,'mmgen-walletgen', args + add_args)
  1139. if ia: return
  1140. t.license()
  1141. t.usr_rand(usr_rand_chars)
  1142. t.passphrase_new('new MMGen wallet',cfg['wpasswd'])
  1143. t.label()
  1144. global have_dfl_wallet
  1145. if not have_dfl_wallet:
  1146. t.expect('move it to the data directory? (Y/n): ',('n','y')[gen_dfl_wallet])
  1147. if gen_dfl_wallet: have_dfl_wallet = True
  1148. t.written_to_file('MMGen wallet')
  1149. t.ok()
  1150. def walletgen_dfl_wallet(self,name,seed_len=None):
  1151. self.walletgen(name,seed_len=seed_len,gen_dfl_wallet=True)
  1152. def brainwalletgen_ref(self,name):
  1153. sl_arg = '-l%s' % cfg['seed_len']
  1154. hp_arg = '-p%s' % ref_wallet_hash_preset
  1155. label = "test.py ref. wallet (pw '%s', seed len %s)" \
  1156. % (ref_wallet_brainpass,cfg['seed_len'])
  1157. bf = 'ref.mmbrain'
  1158. args = ['-d',cfg['tmpdir'],hp_arg,sl_arg,'-ib','-L',label]
  1159. write_to_tmpfile(cfg,bf,ref_wallet_brainpass)
  1160. write_to_tmpfile(cfg,pwfile,cfg['wpasswd'])
  1161. if ia:
  1162. add_args = ['-r0', '-q', '-P%s' % get_tmpfile_fn(cfg,pwfile),
  1163. get_tmpfile_fn(cfg,bf)]
  1164. else:
  1165. add_args = [usr_rand_arg]
  1166. t = MMGenExpect(name,'mmgen-walletconv', args + add_args)
  1167. if ia: return
  1168. t.license()
  1169. t.expect('Enter brainwallet: ', ref_wallet_brainpass+'\n')
  1170. t.passphrase_new('new MMGen wallet',cfg['wpasswd'])
  1171. t.usr_rand(usr_rand_chars)
  1172. sid = os.path.basename(t.written_to_file('MMGen wallet').split('-')[0])
  1173. refcheck('Seed ID',sid,cfg['seed_id'])
  1174. def refwalletgen(self,name): self.brainwalletgen_ref(name)
  1175. def passchg(self,name,wf,pf):
  1176. # ia: reuse password, since there's no way to change it non-interactively
  1177. silence()
  1178. write_to_tmpfile(cfg,pwfile,get_data_from_file(pf))
  1179. end_silence()
  1180. add_args = ([usr_rand_arg],['-q','-r0','-P',pf])[bool(ia)]
  1181. t = MMGenExpect(name,'mmgen-passchg', add_args +
  1182. ['-d',cfg['tmpdir'],'-p','2','-L','Changed label'] + ([],[wf])[bool(wf)])
  1183. if ia: return
  1184. t.license()
  1185. t.passphrase('MMGen wallet',cfgs['1']['wpasswd'],pwtype='old')
  1186. t.expect_getend('Hash preset changed to ')
  1187. t.passphrase('MMGen wallet',cfg['wpasswd'],pwtype='new') # reuse passphrase?
  1188. t.expect('Repeat passphrase: ',cfg['wpasswd']+'\n')
  1189. t.usr_rand(usr_rand_chars)
  1190. # t.expect('Enter a wallet label.*: ','Changed Label\n',regex=True)
  1191. t.expect_getend('Label changed to ')
  1192. # t.expect_getend('Key ID changed: ')
  1193. if not wf:
  1194. t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
  1195. t.written_to_file('New wallet')
  1196. t.expect('Securely deleting old wallet')
  1197. # t.expect('Okay to WIPE 1 regular file ? (Yes/No)','Yes\n')
  1198. t.expect('Wallet passphrase has changed')
  1199. t.expect_getend('has been changed to ')
  1200. else:
  1201. t.written_to_file('MMGen wallet')
  1202. t.ok()
  1203. def passchg_dfl_wallet(self,name,pf):
  1204. if ia:
  1205. m = "\nAnswer 'YES'<ENTER> at the the interactive prompt"
  1206. msg(grnbg(m))
  1207. return self.passchg(name=name,wf=None,pf=pf)
  1208. def walletchk(self,name,wf,pf,desc='MMGen wallet',
  1209. add_args=[],sid=None,pw=False,extra_desc=''):
  1210. args = ([],['-P',pf,'-q'])[bool(ia and pf)]
  1211. hp = cfg['hash_preset'] if 'hash_preset' in cfg else '1'
  1212. wf_arg = ([],[wf])[bool(wf)]
  1213. t = MMGenExpect(name,'mmgen-walletchk',
  1214. add_args+args+['-p',hp]+wf_arg,
  1215. extra_desc=extra_desc)
  1216. if ia:
  1217. if sid:
  1218. n = (' should be','')[desc=='MMGen wallet']
  1219. m = grnbg('Seed ID%s:' % n)
  1220. msg(grnbg('%s %s' % (m,cyan(sid))))
  1221. return
  1222. if desc != 'hidden incognito data':
  1223. t.expect("Getting %s from file '" % (desc))
  1224. if pw:
  1225. t.passphrase(desc,cfg['wpasswd'])
  1226. t.expect(
  1227. ['Passphrase is OK', 'Passphrase.* are correct'],
  1228. regex=True
  1229. )
  1230. chk = t.expect_getend('Valid %s for Seed ID ' % desc)[:8]
  1231. if sid: t.cmp_or_die(chk,sid)
  1232. else: t.ok()
  1233. def walletchk_newpass(self,name,wf,pf):
  1234. return self.walletchk(name,wf,pf,pw=True)
  1235. def walletchk_newpass_dfl_wallet(self,name,pf):
  1236. return self.walletchk_newpass(name,wf=None,pf=pf)
  1237. def delete_dfl_wallet(self,name,pf):
  1238. with open(os.path.join(cfg['tmpdir'],'del_dw_run'),'w') as f: pass
  1239. if opt.no_dw_delete: return True
  1240. for wf in [f for f in os.listdir(g.data_dir) if f[-6:]=='.mmdat']:
  1241. os.unlink(os.path.join(g.data_dir,wf))
  1242. MMGenExpect(name,'')
  1243. global have_dfl_wallet
  1244. have_dfl_wallet = False
  1245. if not ia: ok()
  1246. def addrgen(self,name,wf,pf=None,check_ref=False,ftype='addr',id_str=None,extra_args=[]):
  1247. ftype,chkfile = ((ftype,'{}file_chk'.format(ftype)),('pass','passfile32_chk'))[ftype=='pass32']
  1248. add_args = extra_args + ([],['-q'] + ([],['-P',pf])[bool(pf)])[ia] + (get_segwit_arg(cfg),[])[ftype[:4]=='pass']
  1249. dlist = [id_str] if id_str else []
  1250. t = MMGenExpect(name,'mmgen-{}gen'.format(ftype), add_args +
  1251. ['-d',cfg['tmpdir']] + ([],[wf])[bool(wf)] + dlist + [cfg['{}_idx_list'.format(ftype)]])
  1252. if ia: return
  1253. t.license()
  1254. t.passphrase('MMGen wallet',cfg['wpasswd'])
  1255. t.expect('Passphrase is OK')
  1256. desc = ('address','password')[ftype=='pass']
  1257. chk = t.expect_getend(r'Checksum for {} data .*?: '.format(desc),regex=True)
  1258. if check_ref:
  1259. c = (cfg[chkfile][g.testnet + 2*cfg['segwit']],cfg[chkfile])[ftype=='pass']
  1260. refcheck('address data checksum',chk,c)
  1261. return
  1262. t.written_to_file('Addresses',oo=True)
  1263. t.ok()
  1264. def addrgen_dfl_wallet(self,name,pf=None,check_ref=False):
  1265. return self.addrgen(name,wf=None,pf=pf,check_ref=check_ref)
  1266. def refaddrgen(self,name,wf,pf):
  1267. d = ' (%s-bit seed)' % cfg['seed_len']
  1268. self.addrgen(name,wf,pf=pf,check_ref=True)
  1269. def addrimport(self,name,addrfile):
  1270. add_args = ([],['-q','-t'])[ia]
  1271. outfile = os.path.join(cfg['tmpdir'],'addrfile_w_comments')
  1272. add_comments_to_addr_file(addrfile,outfile)
  1273. t = MMGenExpect(name,'mmgen-addrimport', add_args + [outfile])
  1274. if ia: return
  1275. t.expect_getend(r'Checksum for address data .*\[.*\]: ',regex=True)
  1276. t.expect("Type uppercase 'YES' to confirm: ",'\n')
  1277. vmsg('This is a simulation, so no addresses were actually imported into the tracking\nwallet')
  1278. t.ok(exit_val=1)
  1279. def txcreate_common(self,name,sources=['1'],non_mmgen_input='',do_label=False,txdo_args=[],add_args=[]):
  1280. if opt.verbose or opt.exact_output:
  1281. sys.stderr.write(green('Generating fake tracking wallet info\n'))
  1282. silence()
  1283. ad,tx_data = create_tx_data(sources)
  1284. dfake = create_fake_unspent_data(ad,tx_data,non_mmgen_input)
  1285. write_fake_data_to_file(repr(dfake))
  1286. cmd_args = make_txcreate_cmdline(tx_data)
  1287. end_silence()
  1288. if opt.verbose or opt.exact_output: sys.stderr.write('\n')
  1289. if ia:
  1290. add_args += ['-q']
  1291. m = '\nAnswer the interactive prompts as follows:\n' + \
  1292. " 'y', 'y', 'q', '1-9'<ENTER>, ENTER, ENTER, ENTER, ENTER, 'y'"
  1293. msg(grnbg(m))
  1294. t = MMGenExpect(name,'mmgen-'+('txcreate','txdo')[bool(txdo_args)],['--rbf','-f',tx_fee] + add_args + cmd_args + txdo_args)
  1295. if ia: return
  1296. t.license()
  1297. if txdo_args and add_args: # txdo4
  1298. t.hash_preset('key-address data','1')
  1299. t.passphrase('key-address data',cfgs['14']['kapasswd'])
  1300. t.expect('Check key-to-address validity? (y/N): ','y')
  1301. for num in tx_data:
  1302. t.expect_getend('Getting address data from file ')
  1303. chk=t.expect_getend(r'Checksum for address data .*?: ',regex=True)
  1304. verify_checksum_or_exit(tx_data[num]['chk'],chk)
  1305. # not in tracking wallet warning, (1 + num sources) times
  1306. if t.expect(['Continue anyway? (y/N): ',
  1307. 'Unable to connect to bitcoind']) == 0:
  1308. t.send('y')
  1309. else:
  1310. errmsg(red('Error: unable to connect to bitcoind. Exiting'))
  1311. sys.exit(1)
  1312. for num in tx_data:
  1313. t.expect('Continue anyway? (y/N): ','y')
  1314. t.expect(r"'q'=quit view, .*?:.",'M', regex=True)
  1315. t.expect(r"'q'=quit view, .*?:.",'q', regex=True)
  1316. outputs_list = [(addrs_per_wallet+1)*i + 1 for i in range(len(tx_data))]
  1317. if non_mmgen_input: outputs_list.append(len(tx_data)*(addrs_per_wallet+1) + 1)
  1318. t.expect('Enter a range or space-separated list of outputs to spend: ',
  1319. ' '.join([str(i) for i in outputs_list])+'\n')
  1320. if non_mmgen_input and not txdo_args: t.expect('Accept? (y/N): ','y')
  1321. t.expect('OK? (Y/n): ','y') # fee OK?
  1322. t.expect('OK? (Y/n): ','y') # change OK?
  1323. if do_label:
  1324. t.expect('Add a comment to transaction? (y/N): ','y')
  1325. t.expect('Comment: ',ref_tx_label.encode('utf8')+'\n')
  1326. else:
  1327. t.expect('Add a comment to transaction? (y/N): ','\n')
  1328. t.tx_view()
  1329. if txdo_args: return t
  1330. t.expect('Save transaction? (y/N): ','y')
  1331. t.written_to_file('Transaction')
  1332. t.ok()
  1333. def txcreate(self,name,addrfile):
  1334. self.txcreate_common(name,sources=['1'])
  1335. def txbump(self,name,txfile,prepend_args=[],seed_args=[]):
  1336. args = prepend_args + ['-q','-d',cfg['tmpdir'],txfile] + seed_args
  1337. t = MMGenExpect(name,'mmgen-txbump',args)
  1338. if seed_args:
  1339. t.hash_preset('key-address data','1')
  1340. t.passphrase('key-address data',cfgs['14']['kapasswd'])
  1341. t.expect('Check key-to-address validity? (y/N): ','y')
  1342. t.expect('deduct the fee from (Hit ENTER for the change output): ','1\n')
  1343. # Fee must be > tx_fee + network relay fee (currently 0.00001)
  1344. t.expect('OK? (Y/n): ','\n')
  1345. t.expect('Enter transaction fee: ','124s\n')
  1346. t.expect('OK? (Y/n): ','\n')
  1347. if seed_args: # sign and send
  1348. t.expect('Edit transaction comment? (y/N): ','\n')
  1349. for cnum,desc in ('1','incognito data'),('3','MMGen wallet'),('4','MMGen wallet'):
  1350. t.passphrase(('%s' % desc),cfgs[cnum]['wpasswd'])
  1351. t.expect("Type uppercase 'YES' to confirm: ",'YES\n')
  1352. else:
  1353. t.expect('Add a comment to transaction? (y/N): ','\n')
  1354. t.expect('Save transaction? (y/N): ','y')
  1355. t.written_to_file('Transaction')
  1356. os.unlink(txfile) # our tx file replaces the original
  1357. os.system('touch ' + os.path.join(cfg['tmpdir'],'txbump'))
  1358. t.ok()
  1359. def txdo(self,name,addrfile,wallet):
  1360. t = self.txcreate_common(name,sources=['1'],txdo_args=[wallet])
  1361. self.txsign(name,'','',pf='',save=True,has_label=False,txdo_handle=t)
  1362. self.txsend(name,'',txdo_handle=t)
  1363. def txcreate_dfl_wallet(self,name,addrfile):
  1364. self.txcreate_common(name,sources=['15'])
  1365. def txsign_end(self,t,tnum=None,has_label=False):
  1366. t.expect('Signing transaction')
  1367. cprompt = ('Add a comment to transaction','Edit transaction comment')[has_label]
  1368. t.expect('%s? (y/N): ' % cprompt,'\n')
  1369. t.expect('Save signed transaction.*?\? \(Y/n\): ','y',regex=True)
  1370. add = ' #' + tnum if tnum else ''
  1371. t.written_to_file('Signed transaction' + add, oo=True)
  1372. def txsign(self,name,txfile,wf,pf='',bumpf='',save=True,has_label=False,txdo_handle=None):
  1373. add_args = ([],['-q','-P',pf])[ia]
  1374. if ia:
  1375. m = '\nAnswer the interactive prompts as follows:\n ENTER, ENTER, ENTER'
  1376. msg(grnbg(m))
  1377. if txdo_handle:
  1378. t = txdo_handle
  1379. if ia: return
  1380. else:
  1381. t = MMGenExpect(name,'mmgen-txsign', add_args+['-d',cfg['tmpdir'],txfile]+([],[wf])[bool(wf)])
  1382. if ia: return
  1383. t.license()
  1384. t.tx_view()
  1385. t.passphrase('MMGen wallet',cfg['wpasswd'])
  1386. if txdo_handle: return
  1387. if save:
  1388. self.txsign_end(t,has_label=has_label)
  1389. t.ok()
  1390. else:
  1391. cprompt = ('Add a comment to transaction','Edit transaction comment')[has_label]
  1392. t.expect('%s? (y/N): ' % cprompt,'\n')
  1393. t.expect('Save signed transaction? (Y/n): ','n')
  1394. t.ok(exit_val=1)
  1395. def txsign_dfl_wallet(self,name,txfile,pf='',save=True,has_label=False):
  1396. return self.txsign(name,txfile,wf=None,pf=pf,save=save,has_label=has_label)
  1397. def txsend(self,name,sigfile,txdo_handle=None):
  1398. if txdo_handle:
  1399. t = txdo_handle
  1400. else:
  1401. t = MMGenExpect(name,'mmgen-txsend', ['-d',cfg['tmpdir'],sigfile])
  1402. t.license()
  1403. t.tx_view()
  1404. t.expect('Add a comment to transaction? (y/N): ','\n')
  1405. t.expect('Are you sure you want to broadcast this')
  1406. m = 'YES, I REALLY WANT TO DO THIS'
  1407. t.expect("'%s' to confirm: " % m,m+'\n')
  1408. t.expect('BOGUS transaction NOT sent')
  1409. t.written_to_file('Sent transaction')
  1410. t.ok()
  1411. def walletconv_export(self,name,wf,desc,uargs=[],out_fmt='w',pf=None,out_pw=False):
  1412. opts = ['-d',cfg['tmpdir'],'-o',out_fmt] + uargs + \
  1413. ([],[wf])[bool(wf)] + ([],['-P',pf])[bool(pf)]
  1414. t = MMGenExpect(name,'mmgen-walletconv',opts)
  1415. if ia: return
  1416. t.license()
  1417. if not pf:
  1418. t.passphrase('MMGen wallet',cfg['wpasswd'])
  1419. if out_pw:
  1420. t.passphrase_new('new '+desc,cfg['wpasswd'])
  1421. t.usr_rand(usr_rand_chars)
  1422. if ' '.join(desc.split()[-2:]) == 'incognito data':
  1423. t.expect('Generating encryption key from OS random data ')
  1424. t.expect('Generating encryption key from OS random data ')
  1425. ic_id = t.expect_getend('New Incog Wallet ID: ')
  1426. t.expect('Generating encryption key from OS random data ')
  1427. if desc == 'hidden incognito data':
  1428. write_to_tmpfile(cfg,incog_id_fn,ic_id)
  1429. ret = t.expect(['Create? (Y/n): ',"'YES' to confirm: "])
  1430. if ret == 0:
  1431. t.send('\n')
  1432. t.expect('Enter file size: ',str(hincog_bytes)+'\n')
  1433. else:
  1434. t.send('YES\n')
  1435. if out_fmt == 'w': t.label()
  1436. return t.written_to_file(capfirst(desc),oo=True),t
  1437. def export_seed(self,name,wf,desc='seed data',out_fmt='seed',pf=None):
  1438. f,t = self.walletconv_export(name,wf,desc=desc,out_fmt=out_fmt,pf=pf)
  1439. if ia: return
  1440. silence()
  1441. msg('%s: %s' % (capfirst(desc),cyan(get_data_from_file(f,desc))))
  1442. end_silence()
  1443. t.ok()
  1444. def export_hex(self,name,wf,desc='hexadecimal seed data',out_fmt='hex',pf=None):
  1445. self.export_seed(name,wf,desc=desc,out_fmt=out_fmt,pf=pf)
  1446. def export_seed_dfl_wallet(self,name,pf,desc='seed data',out_fmt='seed'):
  1447. self.export_seed(name,wf=None,desc=desc,out_fmt=out_fmt,pf=pf)
  1448. def export_mnemonic(self,name,wf):
  1449. self.export_seed(name,wf,desc='mnemonic data',out_fmt='words')
  1450. def export_incog(self,name,wf,desc='incognito data',out_fmt='i',add_args=[]):
  1451. uargs = ['-p1',usr_rand_arg] + add_args
  1452. f,t = self.walletconv_export(name,wf,desc=desc,out_fmt=out_fmt,uargs=uargs,out_pw=True)
  1453. t.ok()
  1454. def export_incog_hex(self,name,wf):
  1455. self.export_incog(name,wf,desc='hex incognito data',out_fmt='xi')
  1456. # TODO: make outdir and hidden incog compatible (ignore --outdir and warn user?)
  1457. def export_incog_hidden(self,name,wf):
  1458. rf = os.path.join(cfg['tmpdir'],hincog_fn)
  1459. add_args = ['-J','%s,%s'%(rf,hincog_offset)]
  1460. self.export_incog(
  1461. name,wf,desc='hidden incognito data',out_fmt='hi',add_args=add_args)
  1462. def addrgen_seed(self,name,wf,foo,desc='seed data',in_fmt='seed'):
  1463. stdout = (False,True)[desc=='seed data'] #capture output to screen once
  1464. add_args = ([],['-S'])[bool(stdout)] + get_segwit_arg(cfg)
  1465. t = MMGenExpect(name,'mmgen-addrgen', add_args +
  1466. ['-i'+in_fmt,'-d',cfg['tmpdir'],wf,cfg['addr_idx_list']])
  1467. t.license()
  1468. t.expect_getend('Valid %s for Seed ID ' % desc)
  1469. vmsg('Comparing generated checksum with checksum from previous address file')
  1470. chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True)
  1471. if stdout: t.read()
  1472. verify_checksum_or_exit(get_addrfile_checksum(),chk)
  1473. if in_fmt == 'seed':
  1474. t.ok()
  1475. else:
  1476. t.no_overwrite()
  1477. t.ok(exit_val=1)
  1478. def addrgen_hex(self,name,wf,foo,desc='hexadecimal seed data',in_fmt='hex'):
  1479. self.addrgen_seed(name,wf,foo,desc=desc,in_fmt=in_fmt)
  1480. def addrgen_mnemonic(self,name,wf,foo):
  1481. self.addrgen_seed(name,wf,foo,desc='mnemonic data',in_fmt='words')
  1482. def addrgen_incog(self,name,wf=[],foo='',in_fmt='i',desc='incognito data',args=[]):
  1483. t = MMGenExpect(name,'mmgen-addrgen', args + get_segwit_arg(cfg) + ['-i'+in_fmt,'-d',cfg['tmpdir']]+
  1484. ([],[wf])[bool(wf)] + [cfg['addr_idx_list']])
  1485. t.license()
  1486. t.expect_getend('Incog Wallet ID: ')
  1487. t.hash_preset(desc,'1')
  1488. t.passphrase('%s \w{8}' % desc, cfg['wpasswd'])
  1489. vmsg('Comparing generated checksum with checksum from address file')
  1490. chk = t.expect_getend(r'Checksum for address data .*?: ',regex=True)
  1491. verify_checksum_or_exit(get_addrfile_checksum(),chk)
  1492. t.no_overwrite()
  1493. t.ok(exit_val=1)
  1494. def addrgen_incog_hex(self,name,wf,foo):
  1495. self.addrgen_incog(name,wf,'',in_fmt='xi',desc='hex incognito data')
  1496. def addrgen_incog_hidden(self,name,wf,foo):
  1497. rf = os.path.join(cfg['tmpdir'],hincog_fn)
  1498. self.addrgen_incog(name,[],'',in_fmt='hi',desc='hidden incognito data',
  1499. args=['-H','%s,%s'%(rf,hincog_offset),'-l',str(hincog_seedlen)])
  1500. def keyaddrgen(self,name,wf,pf=None,check_ref=False):
  1501. args = get_segwit_arg(cfg) + ['-d',cfg['tmpdir'],usr_rand_arg,wf,cfg['addr_idx_list']]
  1502. if ia:
  1503. m = "\nAnswer 'n' at the interactive prompt"
  1504. msg(grnbg(m))
  1505. args = ['-q'] + ([],['-P',pf])[bool(pf)] + args
  1506. t = MMGenExpect(name,'mmgen-keygen', args)
  1507. if ia: return
  1508. t.license()
  1509. t.passphrase('MMGen wallet',cfg['wpasswd'])
  1510. chk = t.expect_getend(r'Checksum for key-address data .*?: ',regex=True)
  1511. if check_ref:
  1512. refcheck('key-address data checksum',chk,cfg['keyaddrfile_chk'][g.testnet + 2*cfg['segwit']])
  1513. return
  1514. t.expect('Encrypt key list? (y/N): ','y')
  1515. t.usr_rand(usr_rand_chars)
  1516. t.hash_preset('new key list','1')
  1517. # t.passphrase_new('new key list','kafile password')
  1518. t.passphrase_new('new key list',cfg['kapasswd'])
  1519. t.written_to_file('Encrypted secret keys',oo=True)
  1520. t.ok()
  1521. def refkeyaddrgen(self,name,wf,pf):
  1522. self.keyaddrgen(name,wf,pf,check_ref=True)
  1523. def refpasswdgen(self,name,wf,pf):
  1524. self.addrgen(name,wf,pf,check_ref=True,ftype='pass',id_str='alice@crypto.org')
  1525. def ref_b32passwdgen(self,name,wf,pf):
  1526. ea = ['--base32','--passwd-len','17']
  1527. self.addrgen(name,wf,pf,check_ref=True,ftype='pass32',id_str='фубар@crypto.org',extra_args=ea)
  1528. def txsign_keyaddr(self,name,keyaddr_file,txfile):
  1529. t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],'-M',keyaddr_file,txfile])
  1530. t.license()
  1531. t.hash_preset('key-address data','1')
  1532. t.passphrase('key-address data',cfg['kapasswd'])
  1533. t.expect('Check key-to-address validity? (y/N): ','y')
  1534. t.tx_view()
  1535. self.txsign_end(t)
  1536. t.ok()
  1537. def walletgen2(self,name,del_dw_run='dummy'):
  1538. self.walletgen(name,seed_len=128)
  1539. def addrgen2(self,name,wf):
  1540. self.addrgen(name,wf,pf='')
  1541. def txcreate2(self,name,addrfile):
  1542. self.txcreate_common(name,sources=['2'])
  1543. def txsign2(self,name,txf1,wf1,txf2,wf2):
  1544. t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],txf1,wf1,txf2,wf2])
  1545. t.license()
  1546. for cnum in ('1','2'):
  1547. t.tx_view()
  1548. t.passphrase('MMGen wallet',cfgs[cnum]['wpasswd'])
  1549. self.txsign_end(t,cnum)
  1550. t.ok()
  1551. def export_mnemonic2(self,name,wf):
  1552. self.export_mnemonic(name,wf)
  1553. def walletgen3(self,name,del_dw_run='dummy'):
  1554. self.walletgen(name)
  1555. def addrgen3(self,name,wf):
  1556. self.addrgen(name,wf,pf='')
  1557. def txcreate3(self,name,addrfile1,addrfile2):
  1558. self.txcreate_common(name,sources=['1','3'])
  1559. def txsign3(self,name,wf1,wf2,txf2):
  1560. t = MMGenExpect(name,'mmgen-txsign', ['-d',cfg['tmpdir'],wf1,wf2,txf2])
  1561. t.license()
  1562. t.tx_view()
  1563. for cnum in ('1','3'):
  1564. # t.expect_getend('Getting MMGen wallet data from file ')
  1565. t.passphrase('MMGen wallet',cfgs[cnum]['wpasswd'])
  1566. self.txsign_end(t)
  1567. t.ok()
  1568. def walletgen4(self,name,del_dw_run='dummy'):
  1569. bwf = os.path.join(cfg['tmpdir'],cfg['bw_filename'])
  1570. make_brainwallet_file(bwf)
  1571. seed_len = str(cfg['seed_len'])
  1572. args = ['-d',cfg['tmpdir'],'-p1',usr_rand_arg,'-l'+seed_len,'-ib']
  1573. t = MMGenExpect(name,'mmgen-walletconv', args + [bwf])
  1574. t.license()
  1575. t.passphrase_new('new MMGen wallet',cfg['wpasswd'])
  1576. t.usr_rand(usr_rand_chars)
  1577. t.label()
  1578. t.written_to_file('MMGen wallet')
  1579. t.ok()
  1580. def addrgen4(self,name,wf):
  1581. self.addrgen(name,wf,pf='')
  1582. def txcreate4(self,name,f1,f2,f3,f4,f5,f6):
  1583. self.txcreate_common(name,sources=['1','2','3','4','14'],non_mmgen_input='4',do_label=1)
  1584. def txdo4(self,name,f1,f2,f3,f4,f5,f6,f7,f8,f9,f10,f11,f12):
  1585. non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn)
  1586. add_args = ['-d',cfg['tmpdir'],'-i','brain','-b'+cfg['bw_params'],'-p1','-k',non_mm_fn,'-M',f12]
  1587. t = self.txcreate_common(name,sources=['1','2','3','4','14'],non_mmgen_input='4',do_label=1,txdo_args=[f7,f8,f9,f10],add_args=add_args)
  1588. os.system('rm -f %s/*.sigtx' % cfg['tmpdir'])
  1589. self.txsign4(name,f7,f8,f9,f10,f11,f12,txdo_handle=t)
  1590. self.txsend(name,'',txdo_handle=t)
  1591. os.system('touch ' + os.path.join(cfg['tmpdir'],'txdo'))
  1592. def txbump4(self,name,f1,f2,f3,f4,f5,f6,f7,f8,f9): # f7:txfile,f9:'txdo'
  1593. non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn)
  1594. self.txbump(name,f7,prepend_args=['-p1','-k',non_mm_fn,'-M',f1],seed_args=[f2,f3,f4,f5,f6,f8])
  1595. def txsign4(self,name,f1,f2,f3,f4,f5,f6,txdo_handle=None):
  1596. if txdo_handle:
  1597. t = txdo_handle
  1598. else:
  1599. non_mm_fn = os.path.join(cfg['tmpdir'],non_mmgen_fn)
  1600. a = ['-d',cfg['tmpdir'],'-i','brain','-b'+cfg['bw_params'],'-p1','-k',non_mm_fn,'-M',f6,f1,f2,f3,f4,f5]
  1601. t = MMGenExpect(name,'mmgen-txsign',a)
  1602. t.license()
  1603. t.hash_preset('key-address data','1')
  1604. t.passphrase('key-address data',cfgs['14']['kapasswd'])
  1605. t.expect('Check key-to-address validity? (y/N): ','y')
  1606. t.tx_view()
  1607. for cnum,desc in ('1','incognito data'),('3','MMGen wallet'):
  1608. t.passphrase(('%s' % desc),cfgs[cnum]['wpasswd'])
  1609. if txdo_handle: return
  1610. self.txsign_end(t,has_label=True)
  1611. t.ok()
  1612. def tool_encrypt(self,name,infile=''):
  1613. if infile:
  1614. infn = infile
  1615. else:
  1616. d = os.urandom(1033)
  1617. tmp_fn = cfg['tool_enc_infn']
  1618. write_to_tmpfile(cfg,tmp_fn,d,binary=True)
  1619. infn = get_tmpfile_fn(cfg,tmp_fn)
  1620. if ia:
  1621. pwfn = 'ni_pw'
  1622. write_to_tmpfile(cfg,pwfn,tool_enc_passwd+'\n')
  1623. pre = ['-P', get_tmpfile_fn(cfg,pwfn)]
  1624. app = ['hash_preset=1']
  1625. else:
  1626. pre,app = [],[]
  1627. t = MMGenExpect(name,'mmgen-tool',pre+['-d',cfg['tmpdir'],usr_rand_arg,'encrypt',infn]+app)
  1628. if ia: return
  1629. t.usr_rand(usr_rand_chars)
  1630. t.hash_preset('user data','1')
  1631. t.passphrase_new('user data',tool_enc_passwd)
  1632. t.written_to_file('Encrypted data')
  1633. t.ok()
  1634. # Generate the reference mmenc file
  1635. # def tool_encrypt_ref(self,name):
  1636. # infn = get_tmpfile_fn(cfg,cfg['tool_enc_ref_infn'])
  1637. # write_data_to_file(infn,cfg['tool_enc_reftext'],silent=True)
  1638. # self.tool_encrypt(name,infn)
  1639. def tool_decrypt(self,name,f1,f2):
  1640. of = name + '.out'
  1641. if ia:
  1642. pwfn = 'ni_pw'
  1643. pre = ['-P', get_tmpfile_fn(cfg,pwfn)]
  1644. else:
  1645. pre = []
  1646. t = MMGenExpect(name,'mmgen-tool',
  1647. pre+['-d',cfg['tmpdir'],'decrypt',f2,'outfile='+of,'hash_preset=1'])
  1648. if not ia:
  1649. t.passphrase('user data',tool_enc_passwd)
  1650. t.written_to_file('Decrypted data')
  1651. d1 = read_from_file(f1,binary=True)
  1652. d2 = read_from_file(get_tmpfile_fn(cfg,of),binary=True)
  1653. cmp_or_die(d1,d2,skip_ok=ia)
  1654. def tool_find_incog_data(self,name,f1,f2):
  1655. i_id = read_from_file(f2).rstrip()
  1656. vmsg('Incog ID: %s' % cyan(i_id))
  1657. t = MMGenExpect(name,'mmgen-tool',
  1658. ['-d',cfg['tmpdir'],'find_incog_data',f1,i_id])
  1659. if ia: return
  1660. o = t.expect_getend('Incog data for ID %s found at offset ' % i_id)
  1661. os.unlink(f1)
  1662. cmp_or_die(hincog_offset,int(o))
  1663. # Saved reference file tests
  1664. def ref_wallet_conv(self,name):
  1665. wf = os.path.join(ref_dir,cfg['ref_wallet'])
  1666. self.walletconv_in(name,wf,'MMGen wallet',pw=True,oo=True)
  1667. def ref_mn_conv(self,name,ext='mmwords',desc='Mnemonic data'):
  1668. wf = os.path.join(ref_dir,cfg['seed_id']+'.'+ext)
  1669. self.walletconv_in(name,wf,desc,oo=True)
  1670. def ref_seed_conv(self,name):
  1671. self.ref_mn_conv(name,ext='mmseed',desc='Seed data')
  1672. def ref_hex_conv(self,name):
  1673. self.ref_mn_conv(name,ext='mmhex',desc='Hexadecimal seed data')
  1674. def ref_brain_conv(self,name):
  1675. uopts = ['-i','b','-p','1','-l',str(cfg['seed_len'])]
  1676. self.walletconv_in(name,None,'brainwallet',uopts,oo=True)
  1677. def ref_incog_conv(self,name,wfk='ic_wallet',in_fmt='i',desc='incognito data'):
  1678. uopts = ['-i',in_fmt,'-p','1','-l',str(cfg['seed_len'])]
  1679. wf = os.path.join(ref_dir,cfg[wfk])
  1680. self.walletconv_in(name,wf,desc,uopts,oo=True,pw=True)
  1681. def ref_incox_conv(self,name):
  1682. self.ref_incog_conv(name,in_fmt='xi',wfk='ic_wallet_hex',desc='hex incognito data')
  1683. def ref_hincog_conv(self,name,wfk='hic_wallet',add_uopts=[]):
  1684. ic_f = os.path.join(ref_dir,cfg[wfk])
  1685. uopts = ['-i','hi','-p','1','-l',str(cfg['seed_len'])] + add_uopts
  1686. hi_opt = ['-H','%s,%s' % (ic_f,ref_wallet_incog_offset)]
  1687. self.walletconv_in(name,None,'hidden incognito data',uopts+hi_opt,oo=True,pw=True)
  1688. def ref_hincog_conv_old(self,name):
  1689. self.ref_hincog_conv(name,wfk='hic_wallet_old',add_uopts=['-O'])
  1690. def ref_wallet_conv_out(self,name):
  1691. self.walletconv_out(name,'MMGen wallet','w',pw=True)
  1692. def ref_mn_conv_out(self,name):
  1693. self.walletconv_out(name,'mnemonic data','mn')
  1694. def ref_seed_conv_out(self,name):
  1695. self.walletconv_out(name,'seed data','seed')
  1696. def ref_hex_conv_out(self,name):
  1697. self.walletconv_out(name,'hexadecimal seed data','hexseed')
  1698. def ref_incog_conv_out(self,name):
  1699. self.walletconv_out(name,'incognito data',out_fmt='i',pw=True)
  1700. def ref_incox_conv_out(self,name):
  1701. self.walletconv_out(name,'hex incognito data',out_fmt='xi',pw=True)
  1702. def ref_hincog_conv_out(self,name,extra_uopts=[]):
  1703. ic_f = os.path.join(cfg['tmpdir'],hincog_fn)
  1704. hi_parms = '%s,%s' % (ic_f,ref_wallet_incog_offset)
  1705. sl_parm = '-l' + str(cfg['seed_len'])
  1706. self.walletconv_out(name,
  1707. 'hidden incognito data', 'hi',
  1708. uopts=['-J',hi_parms,sl_parm] + extra_uopts,
  1709. uopts_chk=['-H',hi_parms,sl_parm],
  1710. pw=True
  1711. )
  1712. def ref_wallet_chk(self,name):
  1713. wf = os.path.join(ref_dir,cfg['ref_wallet'])
  1714. if ia:
  1715. write_to_tmpfile(cfg,pwfile,cfg['wpasswd'])
  1716. pf = get_tmpfile_fn(cfg,pwfile)
  1717. else:
  1718. pf = None
  1719. self.walletchk(name,wf,pf=pf,pw=True,sid=cfg['seed_id'])
  1720. def ref_ss_chk(self,name,ss=None):
  1721. wf = os.path.join(ref_dir,'%s.%s' % (cfg['seed_id'],ss.ext))
  1722. self.walletchk(name,wf,pf=None,desc=ss.desc,sid=cfg['seed_id'])
  1723. def ref_seed_chk(self,name):
  1724. from mmgen.seed import SeedFile
  1725. self.ref_ss_chk(name,ss=SeedFile)
  1726. def ref_hex_chk(self,name):
  1727. from mmgen.seed import HexSeedFile
  1728. self.ref_ss_chk(name,ss=HexSeedFile)
  1729. def ref_mn_chk(self,name):
  1730. from mmgen.seed import Mnemonic
  1731. self.ref_ss_chk(name,ss=Mnemonic)
  1732. def ref_brain_chk(self,name,bw_file=ref_bw_file):
  1733. wf = os.path.join(ref_dir,bw_file)
  1734. add_args = ['-l%s' % cfg['seed_len'], '-p'+ref_bw_hash_preset]
  1735. self.walletchk(name,wf,pf=None,add_args=add_args,
  1736. desc='brainwallet',sid=cfg['ref_bw_seed_id'])
  1737. def ref_brain_chk_spc3(self,name):
  1738. self.ref_brain_chk(name,bw_file=ref_bw_file_spc)
  1739. def ref_hincog_chk(self,name,desc='hidden incognito data'):
  1740. for wtype,edesc,of_arg in ('hic_wallet','',[]), \
  1741. ('hic_wallet_old','(old format)',['-O']):
  1742. ic_arg = ['-H%s,%s' % (
  1743. os.path.join(ref_dir,cfg[wtype]),
  1744. ref_wallet_incog_offset
  1745. )]
  1746. slarg = ['-l%s ' % cfg['seed_len']]
  1747. hparg = ['-p1']
  1748. if ia:
  1749. write_to_tmpfile(cfg,pwfile,cfg['wpasswd'])
  1750. add_args = ['-q','-P%s' % get_tmpfile_fn(cfg,pwfile)]
  1751. else:
  1752. add_args = []
  1753. if ia and wtype == 'hic_wallet_old':
  1754. m = grnbg("Answer 'y' at the interactive prompt if Seed ID is")
  1755. n = cyan(cfg['seed_id'])
  1756. msg('\n%s %s' % (m,n))
  1757. if wtype == 'hic_wallet_old' and opt.profile: msg('')
  1758. t = MMGenExpect(name,'mmgen-walletchk',
  1759. add_args + slarg + hparg + of_arg + ic_arg,
  1760. extra_desc=edesc)
  1761. if ia: continue
  1762. t.passphrase(desc,cfg['wpasswd'])
  1763. if wtype == 'hic_wallet_old':
  1764. t.expect('Is the Seed ID correct? (Y/n): ','\n')
  1765. chk = t.expect_getend('Seed ID: ')
  1766. t.close()
  1767. cmp_or_die(cfg['seed_id'],chk)
  1768. def ref_addrfile_chk(self,name,ftype='addr'):
  1769. wf = os.path.join(ref_dir,cfg['ref_'+ftype+'file'])
  1770. if ia:
  1771. m = "\nAnswer the interactive prompts as follows: '1'<ENTER>, ENTER"
  1772. msg(grnbg(m))
  1773. pfn = 'ref_kafile_passwd'
  1774. write_to_tmpfile(cfg,pfn,ref_kafile_pass)
  1775. aa = ['-P',get_tmpfile_fn(cfg,pfn)]
  1776. else:
  1777. aa = []
  1778. t = MMGenExpect(name,'mmgen-tool',aa+[ftype.replace('segwit','')+'file_chksum',wf])
  1779. if ia:
  1780. k = 'ref_%saddrfile_chksum' % ('','key')[ftype == 'keyaddr']
  1781. m = grnbg('Checksum should be:')
  1782. n = cyan(cfg[k])
  1783. msg(grnbg('%s %s' % (m,n)))
  1784. return
  1785. if ftype == 'keyaddr':
  1786. w = 'key-address data'
  1787. t.hash_preset(w,ref_kafile_hash_preset)
  1788. t.passphrase(w,ref_kafile_pass)
  1789. t.expect('Check key-to-address validity? (y/N): ','y')
  1790. o = t.read().strip().split('\n')[-1]
  1791. cmp_or_die(cfg['ref_'+ftype+'file_chksum'],o)
  1792. def ref_keyaddrfile_chk(self,name):
  1793. self.ref_addrfile_chk(name,ftype='keyaddr')
  1794. def ref_passwdfile_chk(self,name):
  1795. self.ref_addrfile_chk(name,ftype='passwd')
  1796. def ref_segwitaddrfile_chk(self,name):
  1797. self.ref_addrfile_chk(name,ftype='segwitaddr')
  1798. # def txcreate8(self,name,addrfile):
  1799. # self.txcreate_common(name,sources=['8'])
  1800. def ref_tx_chk(self,name):
  1801. tf = os.path.join(ref_dir,cfg['ref_tx_file'])
  1802. wf = os.path.join(ref_dir,cfg['ref_wallet'])
  1803. write_to_tmpfile(cfg,pwfile,cfg['wpasswd'])
  1804. pf = get_tmpfile_fn(cfg,pwfile)
  1805. self.txsign(name,tf,wf,pf,save=False,has_label=True)
  1806. def ref_tool_decrypt(self,name):
  1807. f = os.path.join(ref_dir,ref_enc_fn)
  1808. aa = []
  1809. if ia:
  1810. pfn = 'tool_enc_passwd'
  1811. write_to_tmpfile(cfg,pfn,tool_enc_passwd)
  1812. aa = ['-P',get_tmpfile_fn(cfg,pfn)]
  1813. t = MMGenExpect(name,'mmgen-tool',
  1814. aa + ['-q','decrypt',f,'outfile=-','hash_preset=1'])
  1815. if ia: return
  1816. t.passphrase('user data',tool_enc_passwd)
  1817. t.expect(NL,nonl=True)
  1818. import re
  1819. o = re.sub('\r\n','\n',t.read())
  1820. cmp_or_die(sample_text,o)
  1821. # wallet conversion tests
  1822. def walletconv_in(self,name,infile,desc,uopts=[],pw=False,oo=False):
  1823. opts = ['-d',cfg['tmpdir'],'-o','words',usr_rand_arg]
  1824. if_arg = [infile] if infile else []
  1825. d = '(convert)'
  1826. if ia:
  1827. opts += ['-q']
  1828. msg('')
  1829. if pw:
  1830. pfn = 'ni_passwd'
  1831. write_to_tmpfile(cfg,pfn,cfg['wpasswd'])
  1832. opts += ['-P',get_tmpfile_fn(cfg,pfn)]
  1833. if desc == 'brainwallet':
  1834. m = "\nAnswer the interactive prompt as follows: '%s'<ENTER>"
  1835. msg(grnbg(m % ref_wallet_brainpass))
  1836. if '-O' in uopts:
  1837. m = grnbg("Answer 'y' at the interactive prompt if Seed ID is")
  1838. n = cyan(cfg['seed_id'])
  1839. msg('\n%s %s' % (m,n))
  1840. t = MMGenExpect(name,'mmgen-walletconv',opts+uopts+if_arg,extra_desc=d)
  1841. if ia:
  1842. m = grnbg('Seed ID should be:')
  1843. n = cyan(cfg['seed_id'])
  1844. msg(grnbg('%s %s' % (m,n)))
  1845. return
  1846. t.license()
  1847. if desc == 'brainwallet':
  1848. t.expect('Enter brainwallet: ',ref_wallet_brainpass+'\n')
  1849. if pw:
  1850. t.passphrase(desc,cfg['wpasswd'])
  1851. if name[:19] == 'ref_hincog_conv_old':
  1852. t.expect('Is the Seed ID correct? (Y/n): ','\n')
  1853. else:
  1854. t.expect(['Passphrase is OK',' are correct'])
  1855. # Output
  1856. wf = t.written_to_file('Mnemonic data',oo=oo)
  1857. t.close()
  1858. t.ok()
  1859. # back check of result
  1860. if opt.profile: msg('')
  1861. self.walletchk(name,wf,pf=None,
  1862. desc='mnemonic data',
  1863. sid=cfg['seed_id'],
  1864. extra_desc='(check)'
  1865. )
  1866. def walletconv_out(self,name,desc,out_fmt='w',uopts=[],uopts_chk=[],pw=False):
  1867. opts = ['-d',cfg['tmpdir'],'-p1','-o',out_fmt] + uopts
  1868. if ia:
  1869. pfn = 'ni_passwd'
  1870. write_to_tmpfile(cfg,pfn,cfg['wpasswd'])
  1871. l = 'Non-Interactive Test Wallet'
  1872. aa = ['-q','-L',l,'-r0','-P',get_tmpfile_fn(cfg,pfn)]
  1873. if desc == 'hidden incognito data':
  1874. rd = os.urandom(ref_wallet_incog_offset+128)
  1875. write_to_tmpfile(cfg,hincog_fn,rd)
  1876. else:
  1877. aa = [usr_rand_arg]
  1878. infile = os.path.join(ref_dir,cfg['seed_id']+'.mmwords')
  1879. t = MMGenExpect(name,'mmgen-walletconv',aa+opts+[infile],extra_desc='(convert)')
  1880. add_args = ['-l%s' % cfg['seed_len']]
  1881. if ia:
  1882. pfn = 'ni_passwd'
  1883. write_to_tmpfile(cfg,pfn,cfg['wpasswd'])
  1884. pf = get_tmpfile_fn(cfg,pfn)
  1885. if desc != 'hidden incognito data':
  1886. from mmgen.seed import SeedSource
  1887. ext = SeedSource.fmt_code_to_type(out_fmt).ext
  1888. hps = ('',',1')[bool(pw)] # TODO real hp
  1889. pre_ext = '[%s%s].' % (cfg['seed_len'],hps)
  1890. wf = get_file_with_ext(pre_ext+ext,cfg['tmpdir'],no_dot=True)
  1891. else:
  1892. t.license()
  1893. if pw:
  1894. t.passphrase_new('new '+desc,cfg['wpasswd'])
  1895. t.usr_rand(usr_rand_chars)
  1896. if ' '.join(desc.split()[-2:]) == 'incognito data':
  1897. for i in (1,2,3):
  1898. t.expect('Generating encryption key from OS random data ')
  1899. if desc == 'hidden incognito data':
  1900. ret = t.expect(['Create? (Y/n): ',"'YES' to confirm: "])
  1901. if ret == 0:
  1902. t.send('\n')
  1903. t.expect('Enter file size: ',str(hincog_bytes)+'\n')
  1904. else:
  1905. t.send('YES\n')
  1906. if out_fmt == 'w': t.label()
  1907. wf = t.written_to_file(capfirst(desc),oo=True)
  1908. pf = None
  1909. t.ok()
  1910. if desc == 'hidden incognito data':
  1911. add_args += uopts_chk
  1912. wf = None
  1913. if opt.profile: msg('')
  1914. self.walletchk(name,wf,pf=pf,
  1915. desc=desc,sid=cfg['seed_id'],pw=pw,
  1916. add_args=add_args,
  1917. extra_desc='(check)')
  1918. # END methods
  1919. for k in (
  1920. 'ref_wallet_conv',
  1921. 'ref_mn_conv',
  1922. 'ref_seed_conv',
  1923. 'ref_hex_conv',
  1924. 'ref_brain_conv',
  1925. 'ref_incog_conv',
  1926. 'ref_incox_conv',
  1927. 'ref_hincog_conv',
  1928. 'ref_hincog_conv_old',
  1929. 'ref_wallet_conv_out',
  1930. 'ref_mn_conv_out',
  1931. 'ref_seed_conv_out',
  1932. 'ref_hex_conv_out',
  1933. 'ref_incog_conv_out',
  1934. 'ref_incox_conv_out',
  1935. 'ref_hincog_conv_out',
  1936. 'ref_wallet_chk',
  1937. 'refwalletgen',
  1938. 'refaddrgen',
  1939. 'ref_seed_chk',
  1940. 'ref_hex_chk',
  1941. 'ref_mn_chk',
  1942. 'ref_brain_chk',
  1943. 'ref_hincog_chk',
  1944. 'refkeyaddrgen',
  1945. 'refpasswdgen',
  1946. 'ref_b32passwdgen'
  1947. ):
  1948. for i in ('1','2','3'):
  1949. locals()[k+i] = locals()[k]
  1950. for k in ('walletgen','addrgen','keyaddrgen'): locals()[k+'14'] = locals()[k]
  1951. # create temporary dirs
  1952. if not opt.resume and not opt.skip_deps:
  1953. if g.platform == 'win':
  1954. for cfg in sorted(cfgs):
  1955. mk_tmpdir(cfgs[cfg]['tmpdir'])
  1956. else:
  1957. for cfg in sorted(cfgs):
  1958. src = os.path.join(shm_dir,cfgs[cfg]['tmpdir'].split('/')[-1])
  1959. mk_tmpdir(src)
  1960. try:
  1961. os.unlink(cfgs[cfg]['tmpdir'])
  1962. except OSError as e:
  1963. if e.errno != 2: raise
  1964. finally:
  1965. os.symlink(src,cfgs[cfg]['tmpdir'])
  1966. have_dfl_wallet = False
  1967. # main()
  1968. if opt.pause:
  1969. import termios,atexit
  1970. fd = sys.stdin.fileno()
  1971. old = termios.tcgetattr(fd)
  1972. def at_exit():
  1973. termios.tcsetattr(fd, termios.TCSADRAIN, old)
  1974. atexit.register(at_exit)
  1975. start_time = int(time.time())
  1976. def end_msg():
  1977. t = int(time.time()) - start_time
  1978. m = '{} tests performed. Elapsed time: {:02d}:{:02d}\n'
  1979. sys.stderr.write(green(m.format(cmd_total,t/60,t%60)))
  1980. if ia:
  1981. m = 'Please re-check all {} control values against the program output\n'
  1982. sys.stderr.write(m.format(grnbg('HIGHLIGHTED')))
  1983. ts = MMGenTestSuite()
  1984. try:
  1985. if cmd_args:
  1986. for arg in cmd_args:
  1987. if arg in utils:
  1988. globals()[arg](cmd_args[cmd_args.index(arg)+1:])
  1989. sys.exit(0)
  1990. elif 'info_'+arg in cmd_data:
  1991. dirs = cmd_data['info_'+arg][1]
  1992. if dirs: clean(dirs)
  1993. for cmd in cmd_list[arg]:
  1994. check_needs_rerun(ts,cmd,build=True)
  1995. elif arg in meta_cmds:
  1996. for cmd in meta_cmds[arg]:
  1997. check_needs_rerun(ts,cmd,build=True)
  1998. elif arg in cmd_data:
  1999. check_needs_rerun(ts,arg,build=True)
  2000. else:
  2001. die(1,'%s: unrecognized command' % arg)
  2002. else:
  2003. clean()
  2004. for cmd in cmd_data:
  2005. if cmd[:5] == 'info_':
  2006. msg(green('%sTesting %s' % (('\n','')[bool(opt.resume)],cmd_data[cmd][0])))
  2007. continue
  2008. ts.do_cmd(cmd)
  2009. if cmd is not cmd_data.keys()[-1]: do_between()
  2010. except KeyboardInterrupt:
  2011. die(1,'\nExiting at user request')
  2012. except opt.traceback and Exception:
  2013. with open('my.err') as f:
  2014. t = f.readlines()
  2015. if t: msg_r('\n'+yellow(''.join(t[:-1]))+red(t[-1]))
  2016. die(1,blue('Test script exited with error'))
  2017. except:
  2018. sys.stderr = stderr_save
  2019. raise
  2020. end_msg()