gentest.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258
  1. #!/usr/bin/env python
  2. #
  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/gentest.py: Cryptocoin key/address generation tests 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. from binascii import hexlify
  26. # Import these _after_ local path's been added to sys.path
  27. from mmgen.common import *
  28. from mmgen.obj import MMGenAddrType
  29. rounds = 100
  30. opts_data = lambda: {
  31. 'desc': "Test address generation in various ways",
  32. 'usage':'[options] [spec] [rounds | dump file]',
  33. 'options': """
  34. -h, --help Print this help message
  35. --, --longhelp Print help message for long options (common options)
  36. -q, --quiet Produce quieter output
  37. -t, --type=t Specify address type (valid options: 'compressed','segwit','zcash_z')
  38. -v, --verbose Produce more verbose output
  39. """,
  40. 'notes': """
  41. Tests:
  42. A/B: {prog} a:b [rounds] (compare output of two key generators)
  43. Speed: {prog} a [rounds] (test speed of one key generator)
  44. Compare: {prog} a <dump file> (compare output of a key generator against wallet dump)
  45. where a and b are one of:
  46. '1' - native Python ecdsa library (very slow)
  47. '2' - bitcoincore.org's secp256k1 library (default from v0.8.6)
  48. EXAMPLES:
  49. {prog} 1:2 100
  50. (compare output of native Python ECDSA with secp256k1 library, 100 rounds)
  51. {prog} 2:ext 100
  52. (compare output of secp256k1 library with external library (see below), 100 rounds)
  53. {prog} 2 1000
  54. (test speed of secp256k1 library address generation, 1000 rounds)
  55. {prog} 2 my.dump
  56. (compare addrs generated with secp256k1 library to {dn} wallet dump)
  57. External libraries required for the 'ext' generator:
  58. + pyethereum (for ETH,ETC) https://github.com/ethereum/pyethereum
  59. + pycoin (for all other coins) https://github.com/richardkiss/pycoin
  60. """.format(prog='gentest.py',pnm=g.proj_name,snum=rounds,dn=g.proto.daemon_name)
  61. }
  62. sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
  63. cmd_args = opts.init(opts_data,add_opts=['exact_output'])
  64. if not 1 <= len(cmd_args) <= 2: opts.usage()
  65. addr_type = MMGenAddrType(opt.type or g.proto.dfl_mmtype)
  66. def pyethereum_sec2addr(sec):
  67. return sec,eth.privtoaddr(sec).encode('hex')
  68. def zcash_mini_sec2addr(sec):
  69. p = sp.Popen(['zcash-mini','-key','-simple'],stderr=sp.PIPE,stdin=sp.PIPE,stdout=sp.PIPE)
  70. p.stdin.write(sec.wif+'\n')
  71. o = p.stdout.read().split()
  72. return sec.wif,o[0],o[-1]
  73. def pycoin_sec2addr(sec):
  74. if g.testnet: # pycoin/networks/all.py pycoin/networks/legacy_networks.py
  75. coin = { 'BTC':'XTN', 'LTC':'XLT', 'DASH':'tDASH' }[g.coin]
  76. else:
  77. coin = g.coin
  78. key = pcku.parse_key(sec,PREFIX_TRANSFORMS,coin)
  79. if key is None: die(1,"can't parse {}".format(sec))
  80. o = pcku.create_output(sec,key)[0]
  81. # pmsg(o)
  82. suf = ('_uncompressed','')[addr_type.compressed]
  83. wif = o['wif{}'.format(suf)]
  84. addr = o['p2sh_segwit' if addr_type.name == 'segwit' else '{}_address{}'.format(coin,suf)]
  85. return wif,addr
  86. def init_external_prog():
  87. global b_desc,ext_lib,ext_sec2addr,sp,eth,pcku,PREFIX_TRANSFORMS
  88. if addr_type.name == 'zcash_z':
  89. import subprocess as sp
  90. ext_sec2addr = zcash_mini_sec2addr
  91. ext_lib = 'zcash_mini'
  92. elif addr_type.name == 'ethereum':
  93. try:
  94. import ethereum.utils as eth
  95. except:
  96. die(1,"Unable to import 'pyethereum' module. Is pyethereum installed?")
  97. ext_sec2addr = pyethereum_sec2addr
  98. ext_lib = 'pyethereum'
  99. else:
  100. try:
  101. import pycoin.cmds.ku as pcku
  102. except:
  103. die(1,"Unable to import module 'ku'. Is pycoin installed?")
  104. PREFIX_TRANSFORMS = pcku.prefix_transforms_for_network(g.coin)
  105. ext_sec2addr = pycoin_sec2addr
  106. ext_lib = 'pycoin'
  107. b_desc = ext_lib
  108. def match_error(sec,wif,a_addr,b_addr,a,b):
  109. qmsg_r(red('\nERROR: Values do not match!'))
  110. die(3,"""
  111. sec key : {}
  112. WIF key : {}
  113. {a:10}: {}
  114. {b:10}: {}
  115. """.format(sec,wif,a_addr,b_addr,pnm=g.proj_name,a=kg_a.desc,b=b_desc).rstrip())
  116. def compare_test():
  117. m = "Comparing address generators '{}' and '{}' for coin {}"
  118. last_t = time.time()
  119. qmsg(green(m.format(kg_a.desc,(ext_lib if b == 'ext' else kg_b.desc),g.coin)))
  120. for i in range(rounds):
  121. if opt.verbose or time.time() - last_t >= 0.1:
  122. qmsg_r('\rRound %s/%s ' % (i+1,rounds))
  123. last_t = time.time()
  124. sec = PrivKey(os.urandom(32),compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
  125. ph = kg_a.to_pubhex(sec)
  126. a_addr = ag.to_addr(ph)
  127. if opt.type == 'zcash_z':
  128. a_vk = ag.to_viewkey(ph)
  129. if b == 'ext':
  130. if opt.type == 'zcash_z':
  131. b_wif,b_addr,b_vk = ext_sec2addr(sec)
  132. if b_vk != a_vk:
  133. match_error(sec,sec.wif,a_vk,b_vk,a,b)
  134. else:
  135. b_wif,b_addr = ext_sec2addr(sec)
  136. if b_wif != sec.wif:
  137. match_error(sec,sec.wif,sec.wif,b_wif,a,b)
  138. else:
  139. b_addr = ag.to_addr(kg_b.to_pubhex(sec))
  140. vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr))
  141. if a_addr != b_addr:
  142. match_error(sec,sec.wif,a_addr,b_addr,a,ext_lib if b == 'ext' else b)
  143. qmsg_r('\rRound %s/%s ' % (i+1,rounds))
  144. qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
  145. def speed_test():
  146. m = "Testing speed of address generator '{}' for coin {}"
  147. qmsg(green(m.format(kg_a.desc,g.coin)))
  148. from struct import pack,unpack
  149. seed = os.urandom(28)
  150. print 'Incrementing key with each round'
  151. print 'Starting key:', hexlify(seed+pack('I',0))
  152. import time
  153. start = last_t = time.time()
  154. for i in range(rounds):
  155. if time.time() - last_t >= 0.1:
  156. qmsg_r('\rRound %s/%s ' % (i+1,rounds))
  157. last_t = time.time()
  158. sec = PrivKey(seed+pack('I',i),compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
  159. a_addr = ag.to_addr(kg_a.to_pubhex(sec))
  160. vmsg('\nkey: %s\naddr: %s\n' % (sec.wif,a_addr))
  161. qmsg_r('\rRound %s/%s ' % (i+1,rounds))
  162. qmsg('\n{} addresses generated in {:.2f} seconds'.format(rounds,time.time()-start))
  163. def dump_test():
  164. m = "Comparing output of address generator '{}' against wallet dump '{}'"
  165. qmsg(green(m.format(kg_a.desc,cmd_args[1])))
  166. for n,[wif,a_addr] in enumerate(dump,1):
  167. qmsg_r('\rKey %s/%s ' % (n,len(dump)))
  168. try:
  169. sec = PrivKey(wif=wif)
  170. except:
  171. die(2,'\nInvalid {}net WIF address in dump file: {}'.format(('main','test')[g.testnet],wif))
  172. b_addr = ag.to_addr(kg_a.to_pubhex(sec))
  173. if a_addr != b_addr:
  174. match_error(sec,wif,a_addr,b_addr,3,a)
  175. qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
  176. urounds,fh = None,None
  177. dump = []
  178. if len(cmd_args) == 2:
  179. try:
  180. urounds = int(cmd_args[1])
  181. assert urounds > 0
  182. except:
  183. try:
  184. fh = open(cmd_args[1])
  185. except:
  186. die(1,"Second argument must be filename or positive integer")
  187. else:
  188. for line in fh.readlines():
  189. if 'addr=' in line:
  190. x,addr = line.split('addr=')
  191. dump.append([x.split()[0],addr.split()[0]])
  192. if urounds: rounds = urounds
  193. a,b = None,None
  194. b_desc = 'unknown'
  195. try:
  196. a,b = cmd_args[0].split(':')
  197. except:
  198. try:
  199. a = cmd_args[0]
  200. a = int(a)
  201. assert 1 <= a <= len(g.key_generators)
  202. except:
  203. die(1,"First argument must be one or two generator IDs, colon separated")
  204. else:
  205. try:
  206. a = int(a)
  207. assert 1 <= a <= len(g.key_generators)
  208. if b == 'ext':
  209. init_external_prog()
  210. else:
  211. b = int(b)
  212. assert 1 <= b <= len(g.key_generators)
  213. assert a != b
  214. except:
  215. die(1,"%s: invalid generator IDs" % cmd_args[0])
  216. from mmgen.addr import KeyGenerator,AddrGenerator
  217. from mmgen.obj import PrivKey
  218. kg_a = KeyGenerator(addr_type.pubkey_type,a)
  219. ag = AddrGenerator(addr_type.gen_method)
  220. if a and b:
  221. if b != 'ext':
  222. kg_b = KeyGenerator(addr_type.pubkey_type,b)
  223. b_desc = kg_b.desc
  224. compare_test()
  225. elif a and not fh:
  226. speed_test()
  227. elif a and dump:
  228. b_desc = 'dump'
  229. dump_test()
  230. else:
  231. die(2,'Illegal invocation')