|
@@ -30,68 +30,88 @@ sys.path.insert(0,overlay_setup(repo_root))
|
|
|
from mmgen.common import *
|
|
|
from test.include.common import getrand
|
|
|
|
|
|
+results_file = 'gentest.out.json'
|
|
|
+
|
|
|
rounds = 100
|
|
|
opts_data = {
|
|
|
'text': {
|
|
|
'desc': 'Test key/address generation of the MMGen suite in various ways',
|
|
|
- 'usage':'[options] [spec] [rounds | dump file]',
|
|
|
+ 'usage':'[options] <spec> <rounds | dump file>',
|
|
|
'options': """
|
|
|
--h, --help Print this help message
|
|
|
--a, --all Test all coins supported by specified external tool
|
|
|
+-h, --help Print this help message
|
|
|
+--, --longhelp Print help message for long options (common options)
|
|
|
+-a, --all-coins Test all coins supported by specified external tool
|
|
|
-k, --use-internal-keccak-module Force use of the internal keccak module
|
|
|
---, --longhelp Print help message for long options (common options)
|
|
|
--q, --quiet Produce quieter output
|
|
|
--t, --type=t Specify address type (e.g. 'compressed','segwit','zcash_z','bech32')
|
|
|
--v, --verbose Produce more verbose output
|
|
|
+-q, --quiet Produce quieter output
|
|
|
+-s, --save-results Save output of external tool in Compare test to
|
|
|
+ {rf!r}
|
|
|
+-t, --type=t Specify address type (e.g. 'compressed','segwit',
|
|
|
+ 'zcash_z','bech32')
|
|
|
+-v, --verbose Produce more verbose output
|
|
|
""",
|
|
|
'notes': """
|
|
|
TEST TYPES:
|
|
|
|
|
|
- A/B: {prog} A:B [rounds] (compare key generators A and B)
|
|
|
- Speed: {prog} A [rounds] (test speed of key generator A)
|
|
|
- Compare: {prog} A <dump file> (compare generator A to wallet dump)
|
|
|
+ Compare: {prog} A:B <rounds> (compare address generators A and B)
|
|
|
+ Speed: {prog} A <rounds> (test speed of generator A)
|
|
|
+ Dump: {prog} A <dump file> (compare generator A to wallet dump)
|
|
|
+
|
|
|
+ where:
|
|
|
+
|
|
|
+ A and B are keygen backend numbers ('1' being the default); or
|
|
|
+ B is the name of an external tool (see below) or 'ext'.
|
|
|
|
|
|
- where A and B are one of:
|
|
|
- '1' - native Python ECDSA library (slow), or
|
|
|
- '2' - bitcoincore.org's libsecp256k1 library (default);
|
|
|
- or:
|
|
|
- B is name of an external tool (see below) or 'ext'.
|
|
|
- If B is 'ext', the external tool will be chosen automatically.
|
|
|
+ If B is 'ext', the external tool will be chosen automatically.
|
|
|
+
|
|
|
+ For the Compare test, A may be 'all' to test all backends for the current
|
|
|
+ coin/address type combination.
|
|
|
|
|
|
EXAMPLES:
|
|
|
|
|
|
- Compare addresses generated by native Python ECDSA library and libsecp256k1,
|
|
|
- 100 rounds:
|
|
|
+ Compare addresses generated by 'libsecp256k1' and 'python-ecdsa' backends,
|
|
|
+ with 100 random rounds plus private-key edge cases:
|
|
|
$ {prog} 1:2 100
|
|
|
|
|
|
- Compare mmgen-secp256k1 Segwit address generation to pycoin library for all
|
|
|
- supported coins, 100 rounds:
|
|
|
- $ {prog} --all --type=segwit 2:pycoin 100
|
|
|
+ Compare Segwit addresses from default 'libsecp256k1' backend to 'pycoin'
|
|
|
+ library for all supported coins, 100 rounds + edge cases:
|
|
|
+ $ {prog} --all-coins --type=segwit 1:pycoin 100
|
|
|
+
|
|
|
+ Compare addresses from 'python-ecdsa' backend to output of 'keyconv' tool
|
|
|
+ for all supported coins, 100 rounds + edge cases:
|
|
|
+ $ {prog} --all-coins --type=compressed 2:keyconv 100
|
|
|
|
|
|
- Compare mmgen-secp256k1 address generation to keyconv tool for all
|
|
|
- supported coins, 100 rounds:
|
|
|
- $ {prog} --all --type=compressed 2:keyconv 100
|
|
|
+ Compare bech32 addrs from 'libsecp256k1' backend to Bitcoin Core wallet
|
|
|
+ dump:
|
|
|
+ $ {prog} --type=bech32 1 bech32wallet.dump
|
|
|
|
|
|
- Compare mmgen-secp256k1 XMR address generation to configured external tool,
|
|
|
- 10 rounds:
|
|
|
- $ {prog} --coin=xmr 2:ext 10
|
|
|
+ Compare addresses from Monero 'ed25519ll' backend to output of default
|
|
|
+ external tool, 10 rounds + edge cases:
|
|
|
+ $ {prog} --coin=xmr 3:ext 10
|
|
|
|
|
|
- Test speed of mmgen-secp256k1 address generation, 10,000 rounds:
|
|
|
- $ {prog} 2 10000
|
|
|
+ Test the speed of default Monero 'nacl' backend, 10,000 rounds:
|
|
|
+ $ test/gentest.py --coin=xmr 1 10000
|
|
|
|
|
|
- Compare mmgen-secp256k1-generated bech32 addrs to coin daemon wallet dump:
|
|
|
- $ {prog} --type=bech32 2 bech32wallet.dump
|
|
|
+ Same for Zcash:
|
|
|
+ $ test/gentest.py --coin=zec --type=zcash_z 1 10000
|
|
|
|
|
|
-Supported external tools:
|
|
|
+ Test all configured Monero backends against 'moneropy' library, 3 rounds
|
|
|
+ + edge cases:
|
|
|
+ $ test/gentest.py --coin=xmr all:moneropy 3
|
|
|
+
|
|
|
+ Test 'nacl' and 'ed25519ll_djbec' backends against each other, 10,000 rounds
|
|
|
+ + edge cases:
|
|
|
+ $ test/gentest.py --coin=xmr 1:2 10000
|
|
|
+
|
|
|
+SUPPORTED EXTERNAL TOOLS:
|
|
|
|
|
|
+ ethkey (for ETH,ETC)
|
|
|
- https://github.com/openethereum/openethereum
|
|
|
+ https://github.com/openethereum/openethereum
|
|
|
(build with 'cargo build -p ethkey-cli --release')
|
|
|
|
|
|
- + zcash-mini (for Zcash Z-addresses)
|
|
|
+ + zcash-mini (for Zcash-Z addresses and view keys)
|
|
|
https://github.com/FiloSottile/zcash-mini
|
|
|
|
|
|
- + moneropy (for Monero addresses)
|
|
|
+ + moneropy (for Monero addresses and view keys)
|
|
|
https://github.com/bigreddmachine/MoneroPy
|
|
|
|
|
|
+ pycoin (for supported coins)
|
|
@@ -103,6 +123,9 @@ Supported external tools:
|
|
|
"""
|
|
|
},
|
|
|
'code': {
|
|
|
+ 'options': lambda s: s.format(
|
|
|
+ rf=results_file,
|
|
|
+ ),
|
|
|
'notes': lambda s: s.format(
|
|
|
prog='test/gentest.py',
|
|
|
pnm=g.proj_name,
|
|
@@ -110,36 +133,33 @@ Supported external tools:
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
|
|
-
|
|
|
-cmd_args = opts.init(opts_data,add_opts=['exact_output'])
|
|
|
-
|
|
|
-if not 1 <= len(cmd_args) <= 2:
|
|
|
- opts.usage()
|
|
|
-
|
|
|
-from mmgen.protocol import init_proto_from_opts
|
|
|
-proto = init_proto_from_opts()
|
|
|
+gtr = namedtuple('gen_tool_result',['wif','addr','vk'])
|
|
|
|
|
|
-from subprocess import run,PIPE,DEVNULL
|
|
|
def get_cmd_output(cmd,input=None):
|
|
|
return run(cmd,input=input,stdout=PIPE,stderr=DEVNULL).stdout.decode().splitlines()
|
|
|
|
|
|
-from collections import namedtuple
|
|
|
-gtr = namedtuple('gen_tool_result',['wif','addr','vk'])
|
|
|
+saved_results = {}
|
|
|
|
|
|
class GenTool(object):
|
|
|
|
|
|
+ def __init__(self,proto,addr_type):
|
|
|
+ self.proto = proto
|
|
|
+ self.addr_type = addr_type
|
|
|
+ self.data = {}
|
|
|
+
|
|
|
+ def __del__(self):
|
|
|
+ if opt.save_results:
|
|
|
+ key = f'{self.proto.coin}-{self.proto.network}-{self.addr_type.name}-{self.desc}'.lower()
|
|
|
+ saved_results[key] = self.data
|
|
|
+
|
|
|
def run_tool(self,sec):
|
|
|
- vcoin = 'BTC' if proto.coin == 'BCH' else proto.coin
|
|
|
- return self.run(sec,vcoin)
|
|
|
+ vcoin = 'BTC' if self.proto.coin == 'BCH' else self.proto.coin
|
|
|
+ ret = self.run(sec,vcoin)
|
|
|
+ self.data[sec] = ret._asdict()
|
|
|
+ return ret
|
|
|
|
|
|
class GenToolEthkey(GenTool):
|
|
|
desc = 'ethkey'
|
|
|
- def __init__(self):
|
|
|
- proto = init_proto('eth')
|
|
|
- global addr_type
|
|
|
- addr_type = MMGenAddrType(proto,'E')
|
|
|
-
|
|
|
def run(self,sec,vcoin):
|
|
|
o = get_cmd_output(['ethkey','info',sec])
|
|
|
return gtr(o[0].split()[1],o[-1].split()[1],None)
|
|
@@ -152,11 +172,6 @@ class GenToolKeyconv(GenTool):
|
|
|
|
|
|
class GenToolZcash_mini(GenTool):
|
|
|
desc = 'zcash-mini'
|
|
|
- def __init__(self):
|
|
|
- proto = init_proto('zec')
|
|
|
- global addr_type
|
|
|
- addr_type = MMGenAddrType(proto,'Z')
|
|
|
-
|
|
|
def run(self,sec,vcoin):
|
|
|
o = get_cmd_output(['zcash-mini','-key','-simple'],input=(sec.wif+'\n').encode())
|
|
|
return gtr(o[1],o[0],o[-1])
|
|
@@ -166,24 +181,26 @@ class GenToolPycoin(GenTool):
|
|
|
pycoin/networks/all.py pycoin/networks/legacy_networks.py
|
|
|
"""
|
|
|
desc = 'pycoin'
|
|
|
- def __init__(self):
|
|
|
- m = "Unable to import pycoin.networks.registry. Is pycoin installed on your system?"
|
|
|
+ def __init__(self,*args,**kwargs):
|
|
|
+ super().__init__(*args,**kwargs)
|
|
|
try:
|
|
|
from pycoin.networks.registry import network_for_netcode
|
|
|
except:
|
|
|
- raise ImportError(m)
|
|
|
+ raise ImportError('Unable to import pycoin.networks.registry. Is pycoin installed on your system?')
|
|
|
self.nfnc = network_for_netcode
|
|
|
|
|
|
def run(self,sec,vcoin):
|
|
|
- if proto.testnet:
|
|
|
+ if self.proto.testnet:
|
|
|
vcoin = ci.external_tests['testnet']['pycoin'][vcoin]
|
|
|
network = self.nfnc(vcoin)
|
|
|
- key = network.keys.private(secret_exponent=int(sec,16),is_compressed=addr_type.name != 'legacy')
|
|
|
+ key = network.keys.private(
|
|
|
+ secret_exponent = int(sec,16),
|
|
|
+ is_compressed = self.addr_type.name != 'legacy' )
|
|
|
if key is None:
|
|
|
die(1,f'can’t parse {sec}')
|
|
|
- if addr_type.name in ('segwit','bech32'):
|
|
|
+ if self.addr_type.name in ('segwit','bech32'):
|
|
|
hash160_c = key.hash160(is_compressed=True)
|
|
|
- if addr_type.name == 'segwit':
|
|
|
+ if self.addr_type.name == 'segwit':
|
|
|
p2sh_script = network.contract.for_p2pkh_wit(hash160_c)
|
|
|
addr = network.address.for_p2s(p2sh_script)
|
|
|
else:
|
|
@@ -194,44 +211,42 @@ class GenToolPycoin(GenTool):
|
|
|
|
|
|
class GenToolMoneropy(GenTool):
|
|
|
desc = 'moneropy'
|
|
|
- def __init__(self):
|
|
|
|
|
|
- m = "Unable to import moneropy. Is moneropy installed on your system?"
|
|
|
+ def __init__(self,*args,**kwargs):
|
|
|
+ super().__init__(*args,**kwargs)
|
|
|
try:
|
|
|
import moneropy.account
|
|
|
except:
|
|
|
- raise ImportError(m)
|
|
|
-
|
|
|
+ raise ImportError('Unable to import moneropy. Is moneropy installed on your system?')
|
|
|
self.mpa = moneropy.account
|
|
|
- proto = init_proto('xmr')
|
|
|
-
|
|
|
- global addr_type
|
|
|
- addr_type = MMGenAddrType(proto,'M')
|
|
|
|
|
|
def run(self,sec,vcoin):
|
|
|
- sk_t,vk_t,addr_t = self.mpa.account_from_spend_key(sec) # VERY slow!
|
|
|
- return gtr(sk_t,addr_t,vk_t)
|
|
|
+ if sec in self.data:
|
|
|
+ return gtr(**self.data[sec])
|
|
|
+ else:
|
|
|
+ sk,vk,addr = self.mpa.account_from_spend_key(sec) # VERY slow!
|
|
|
+ return gtr(sk,addr,vk)
|
|
|
+
|
|
|
+def find_or_check_tool(proto,addr_type,toolname):
|
|
|
|
|
|
-def get_tool(arg):
|
|
|
+ ext_progs = list(ci.external_tests[proto.network])
|
|
|
|
|
|
- if arg not in ext_progs + ['ext']:
|
|
|
- die(1,f'{arg!r}: unsupported tool for network {proto.network}')
|
|
|
+ if toolname not in ext_progs + ['ext']:
|
|
|
+ die(1,f'{toolname!r}: unsupported tool for network {proto.network}')
|
|
|
|
|
|
- if opt.all:
|
|
|
- if arg == 'ext':
|
|
|
- die(1,"'--all' must be combined with a specific external testing tool")
|
|
|
- return arg
|
|
|
+ if opt.all_coins and toolname == 'ext':
|
|
|
+ die(1,"'--all-coins' must be combined with a specific external testing tool")
|
|
|
else:
|
|
|
tool = ci.get_test_support(
|
|
|
proto.coin,
|
|
|
addr_type.name,
|
|
|
proto.network,
|
|
|
verbose = not opt.quiet,
|
|
|
- tool = arg if arg in ext_progs else None )
|
|
|
- if not tool:
|
|
|
- sys.exit(2)
|
|
|
- if arg in ext_progs and arg != tool:
|
|
|
+ tool = toolname if toolname != 'ext' else None )
|
|
|
+ if tool and toolname in ext_progs and toolname != tool:
|
|
|
sys.exit(3)
|
|
|
+ if tool == None:
|
|
|
+ return None
|
|
|
return tool
|
|
|
|
|
|
def test_equal(desc,a_val,b_val,in_bytes,sec,wif,a_desc,b_desc):
|
|
@@ -251,76 +266,120 @@ def test_equal(desc,a_val,b_val,in_bytes,sec,wif,a_desc,b_desc):
|
|
|
w=max(len(e) for e in (a_desc,b_desc)) + 1
|
|
|
).rstrip())
|
|
|
|
|
|
-def gentool_test(kg_a,kg_b,ag,rounds):
|
|
|
-
|
|
|
- m = "Comparing address generators '{A}' and '{B}' for {N} {c} ({n}), addrtype {a!r}"
|
|
|
- e = ci.get_entry(proto.coin,proto.network)
|
|
|
- qmsg(green(m.format(
|
|
|
- A = kg_a.desc,
|
|
|
- B = kg_b.desc,
|
|
|
- N = proto.network,
|
|
|
- c = proto.coin,
|
|
|
- n = e.name if e else '---',
|
|
|
- a = addr_type.name )))
|
|
|
-
|
|
|
- global last_t
|
|
|
- last_t = time.time()
|
|
|
+def do_ab_test(proto,addr_type,kg_b,rounds,backend_num):
|
|
|
|
|
|
- def do_compare_test(n,trounds,in_bytes):
|
|
|
+ def run_ab_inner(n,trounds,in_bytes):
|
|
|
global last_t
|
|
|
if opt.verbose or time.time() - last_t >= 0.1:
|
|
|
qmsg_r(f'\rRound {i+1}/{trounds} ')
|
|
|
last_t = time.time()
|
|
|
sec = PrivKey(proto,in_bytes,compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
|
|
|
- a_ph = kg_a.to_pubhex(sec)
|
|
|
- a_addr = ag.to_addr(a_ph)
|
|
|
+ data = kg_a.to_pubhex(sec)
|
|
|
+ ag = AddrGenerator(proto,addr_type)
|
|
|
+ a_addr = ag.to_addr(data)
|
|
|
+ tinfo = (in_bytes,sec,sec.wif,type(kg_a).__name__,type(kg_b).__name__)
|
|
|
a_vk = None
|
|
|
- tinfo = (in_bytes,sec,sec.wif,kg_a.desc,kg_b.desc)
|
|
|
+
|
|
|
+ def do_msg():
|
|
|
+ vmsg( fs.format( b=in_bytes.hex(), r=sec, k=sec.wif, v=a_vk, a=a_addr ))
|
|
|
+
|
|
|
if isinstance(kg_b,GenTool):
|
|
|
- b = kg_b.run_tool(sec)
|
|
|
- test_equal('WIF keys',sec.wif,b.wif,*tinfo)
|
|
|
- test_equal('addresses',a_addr,b.addr,*tinfo)
|
|
|
- if b.vk:
|
|
|
- a_vk = ag.to_viewkey(a_ph)
|
|
|
- test_equal('view keys',a_vk,b.vk,*tinfo)
|
|
|
+ def run_tool():
|
|
|
+ b = kg_b.run_tool(sec)
|
|
|
+ test_equal('WIF keys',sec.wif,b.wif,*tinfo)
|
|
|
+ test_equal('addresses',a_addr,b.addr,*tinfo)
|
|
|
+ if b.vk:
|
|
|
+ test_equal( 'view keys', ag.to_viewkey(data), b.vk, *tinfo )
|
|
|
+ return b.vk
|
|
|
+ a_vk = run_tool()
|
|
|
+ do_msg()
|
|
|
else:
|
|
|
- b_addr = ag.to_addr(kg_b.to_pubhex(sec))
|
|
|
- test_equal('addresses',a_addr,b_addr,*tinfo)
|
|
|
- vmsg(fs.format(b=in_bytes.hex(),k=sec.wif,v=a_vk,a=a_addr))
|
|
|
+ test_equal( 'addresses', a_addr, ag.to_addr(kg_b.to_pubhex(sec)), *tinfo )
|
|
|
+ do_msg()
|
|
|
+
|
|
|
qmsg_r(f'\rRound {n+1}/{trounds} ')
|
|
|
|
|
|
- fs = ( '\ninput: {b}\n%-9s {k}\naddr: {a}\n',
|
|
|
- '\ninput: {b}\n%-9s {k}\nviewkey: {v}\naddr: {a}\n')[
|
|
|
- 'viewkey' in addr_type.extra_attrs] % (addr_type.wif_label + ':')
|
|
|
+ kg_a = KeyGenerator(proto,addr_type,backend_num)
|
|
|
+ if type(kg_a) == type(kg_b):
|
|
|
+ rdie(1,'Key generators are the same!')
|
|
|
+
|
|
|
+ e = ci.get_entry(proto.coin,proto.network)
|
|
|
+ qmsg(green("Comparing address generators '{A}' and '{B}' for {N} {c} ({n}), addrtype {a!r}".format(
|
|
|
+ A = type(kg_a).__name__,
|
|
|
+ B = type(kg_b).__name__.replace('GenTool','').replace('_','-').lower(),
|
|
|
+ N = proto.network,
|
|
|
+ c = proto.coin,
|
|
|
+ n = e.name if e else '---',
|
|
|
+ a = addr_type.name )))
|
|
|
+
|
|
|
+ global last_t
|
|
|
+ last_t = time.time()
|
|
|
+
|
|
|
+ fs = (
|
|
|
+ '\ninput: {b}' +
|
|
|
+ '\nreduced: {r}' +
|
|
|
+ '\n{:9} {{k}}'.format(addr_type.wif_label+':') +
|
|
|
+ ('\nviewkey: {v}' if 'viewkey' in addr_type.extra_attrs else '') +
|
|
|
+ '\naddr: {a}\n' )
|
|
|
+
|
|
|
+ ge = CoinProtocol.Secp256k1.secp256k1_ge
|
|
|
|
|
|
# test some important private key edge cases:
|
|
|
edgecase_sks = (
|
|
|
bytes([0x00]*31 + [0x01]), # min
|
|
|
bytes([0xff]*32), # max
|
|
|
- bytes([0x0f] + [0xff]*31), # same key as above for zcash-z
|
|
|
+ bytes([0x0f] + [0xff]*31), # produces same key as above for zcash-z
|
|
|
+ int.to_bytes(ge + 1, 32, 'big'), # bitcoin will reduce
|
|
|
+ int.to_bytes(ge - 1, 32, 'big'), # bitcoin will not reduce
|
|
|
bytes([0x00]*31 + [0xff]), # monero will reduce
|
|
|
bytes([0xff]*31 + [0x0f]), # monero will not reduce
|
|
|
+ bytes.fromhex('deadbeef'*8),
|
|
|
)
|
|
|
|
|
|
qmsg(purple('edge cases:'))
|
|
|
for i,in_bytes in enumerate(edgecase_sks):
|
|
|
- do_compare_test(i,len(edgecase_sks),in_bytes)
|
|
|
+ run_ab_inner(i,len(edgecase_sks),in_bytes)
|
|
|
qmsg(green('\rOK ' if opt.verbose else 'OK'))
|
|
|
|
|
|
qmsg(purple('random input:'))
|
|
|
for i in range(rounds):
|
|
|
- do_compare_test(i,rounds,getrand(32))
|
|
|
+ run_ab_inner(i,rounds,getrand(32))
|
|
|
qmsg(green('\rOK ' if opt.verbose else 'OK'))
|
|
|
|
|
|
-def speed_test(kg,ag,rounds):
|
|
|
- m = "Testing speed of address generator '{}' for coin {}"
|
|
|
- qmsg(green(m.format(kg.desc,proto.coin)))
|
|
|
+def init_tool(proto,addr_type,toolname):
|
|
|
+ return globals()['GenTool'+capfirst(toolname.replace('-','_'))](proto,addr_type)
|
|
|
+
|
|
|
+def get_backends(proto,foo):
|
|
|
+ return (1,) if isinstance(proto,CoinProtocol.Zcash) else (1,2)
|
|
|
+
|
|
|
+def ab_test(proto,gen_num,rounds,toolname_or_gen2_num):
|
|
|
+
|
|
|
+ addr_type = MMGenAddrType( proto=proto, id_str=opt.type or proto.dfl_mmtype )
|
|
|
+
|
|
|
+ if is_int(toolname_or_gen2_num):
|
|
|
+ assert gen_num != 'all', "'all' must be used only with external tool"
|
|
|
+ tool = KeyGenerator( proto, addr_type, int(toolname_or_gen2_num) )
|
|
|
+ else:
|
|
|
+ toolname = find_or_check_tool( proto, addr_type, toolname_or_gen2_num )
|
|
|
+ if toolname == None:
|
|
|
+ ymsg(f'Warning: skipping tool {toolname_or_gen2_num!r} for {proto.coin} {addr_type.name}')
|
|
|
+ return
|
|
|
+ tool = init_tool( proto, addr_type, toolname )
|
|
|
+
|
|
|
+ if gen_num == 'all': # check all backends against external tool
|
|
|
+ for n in range(len(get_backends(proto,addr_type.pubkey_type))):
|
|
|
+ do_ab_test( proto, addr_type, tool, rounds, n+1 )
|
|
|
+ else: # check specific backend against external tool or another backend
|
|
|
+ do_ab_test( proto, addr_type, tool, rounds, int(gen_num) )
|
|
|
+
|
|
|
+def speed_test(proto,addr_type,kg,ag,rounds):
|
|
|
+ qmsg(green('Testing speed of address generator {!r} for coin {}'.format(
|
|
|
+ type(kg).__name__,
|
|
|
+ proto.coin )))
|
|
|
from struct import pack,unpack
|
|
|
seed = getrand(28)
|
|
|
qmsg('Incrementing key with each round')
|
|
|
- qmsg('Starting key: {}'.format(
|
|
|
- (seed + pack('I',0)).hex()
|
|
|
- ))
|
|
|
+ qmsg('Starting key: {}'.format( (seed + pack('I',0)).hex() ))
|
|
|
import time
|
|
|
start = last_t = time.time()
|
|
|
|
|
@@ -328,7 +387,7 @@ def speed_test(kg,ag,rounds):
|
|
|
if time.time() - last_t >= 0.1:
|
|
|
qmsg_r(f'\rRound {i+1}/{rounds} ')
|
|
|
last_t = time.time()
|
|
|
- sec = PrivKey(proto,seed+pack('I',i),compressed=addr_type.compressed,pubkey_type=addr_type.pubkey_type)
|
|
|
+ sec = PrivKey( proto, seed+pack('I', i), compressed=addr_type.compressed, pubkey_type=addr_type.pubkey_type )
|
|
|
addr = ag.to_addr(kg.to_pubhex(sec))
|
|
|
vmsg(f'\nkey: {sec.wif}\naddr: {addr}\n')
|
|
|
qmsg(
|
|
@@ -337,15 +396,18 @@ def speed_test(kg,ag,rounds):
|
|
|
('' if g.test_suite_deterministic else f' in {time.time()-start:.2f} seconds')
|
|
|
)
|
|
|
|
|
|
-def dump_test(kg,ag,fh):
|
|
|
+def dump_test(proto,kg,ag,filename):
|
|
|
|
|
|
- dump = [[*(e.split()[0] for e in line.split('addr='))] for line in fh.readlines() if 'addr=' in line]
|
|
|
- if not dump:
|
|
|
- die(1,f'File {fh.name!r} appears not to be a wallet dump')
|
|
|
- fh.close()
|
|
|
+ with open(filename) as fp:
|
|
|
+ dump = [[*(e.split()[0] for e in line.split('addr='))] for line in fp.readlines() if 'addr=' in line]
|
|
|
+ if not dump:
|
|
|
+ die(1,f'File {filename!r} appears not to be a wallet dump')
|
|
|
|
|
|
- m = 'Comparing output of address generator {!r} against wallet dump {!r}'
|
|
|
- qmsg(green(m.format(kg.desc,fh.name)))
|
|
|
+ qmsg(green(
|
|
|
+ "A: generator pair '{}:{}'\nB: wallet dump {!r}".format(
|
|
|
+ type(kg).__name__,
|
|
|
+ type(ag).__name__,
|
|
|
+ filename)))
|
|
|
|
|
|
for count,(b_wif,b_addr) in enumerate(dump,1):
|
|
|
qmsg_r(f'\rKey {count}/{len(dump)} ')
|
|
@@ -355,102 +417,90 @@ def dump_test(kg,ag,fh):
|
|
|
die(2,f'\nInvalid {proto.network} WIF address in dump file: {b_wif}')
|
|
|
a_addr = ag.to_addr(kg.to_pubhex(b_sec))
|
|
|
vmsg(f'\nwif: {b_wif}\naddr: {b_addr}\n')
|
|
|
- tinfo = (bytes.fromhex(b_sec),b_sec,b_wif,kg.desc,fh.name)
|
|
|
+ tinfo = (b_sec,b_sec,b_wif,type(kg).__name__,filename)
|
|
|
test_equal('addresses',a_addr,b_addr,*tinfo)
|
|
|
+
|
|
|
qmsg(green(('\n','')[bool(opt.verbose)] + 'OK'))
|
|
|
|
|
|
-def init_tool(tname):
|
|
|
- return globals()['GenTool'+capfirst(tname.replace('-','_'))]()
|
|
|
+def get_protos(proto,addr_type,toolname):
|
|
|
|
|
|
-def parse_arg1(arg,arg_id):
|
|
|
+ init_genonly_altcoins(testnet=proto.testnet)
|
|
|
|
|
|
- m1 = 'First argument must be a numeric generator ID or two colon-separated generator IDs'
|
|
|
- m2 = 'Second part of first argument must be a numeric generator ID or one of {}'
|
|
|
+ for coin in ci.external_tests[proto.network][toolname]:
|
|
|
+ if coin.lower() not in CoinProtocol.coins:
|
|
|
+ continue
|
|
|
+ ret = init_proto(coin,testnet=proto.testnet)
|
|
|
+ if addr_type not in ret.mmtypes:
|
|
|
+ continue
|
|
|
+ yield ret
|
|
|
|
|
|
- def check_gen_num(n):
|
|
|
- if not (1 <= int(n) <= len(g.key_generators)):
|
|
|
- die(1,f'{n}: invalid generator ID')
|
|
|
- return int(n)
|
|
|
+def parse_args():
|
|
|
+
|
|
|
+ if len(cmd_args) != 2:
|
|
|
+ opts.usage()
|
|
|
+
|
|
|
+ arg1,arg2 = cmd_args
|
|
|
+ pa = namedtuple('parsed_args',['test','gen_num','rounds','arg'])
|
|
|
+
|
|
|
+ if is_int(arg1) and is_int(arg2):
|
|
|
+ return pa( test='speed', gen_num=arg1, rounds=int(arg2), arg=None )
|
|
|
+
|
|
|
+ if is_int(arg1) and os.access(arg2,os.R_OK):
|
|
|
+ return pa( test='dump', gen_num=arg1, rounds=None, arg=arg2 )
|
|
|
+
|
|
|
+ if not is_int(arg2):
|
|
|
+ die(1,'Second argument must be dump filename or integer rounds specification')
|
|
|
|
|
|
- if arg_id == 'a':
|
|
|
- if is_int(arg):
|
|
|
- a_num = check_gen_num(arg)
|
|
|
- return (KeyGenerator(proto,addr_type,a_num),a_num)
|
|
|
- else:
|
|
|
- die(1,m1)
|
|
|
- elif arg_id == 'b':
|
|
|
- if is_int(arg):
|
|
|
- return KeyGenerator(proto,addr_type,check_gen_num(arg))
|
|
|
- elif arg in ext_progs + ['ext']:
|
|
|
- return init_tool(get_tool(arg))
|
|
|
- else:
|
|
|
- die(1,m2.format(ext_progs))
|
|
|
-
|
|
|
-def parse_arg2():
|
|
|
- m = 'Second argument must be dump filename or integer rounds specification'
|
|
|
- if len(cmd_args) == 1:
|
|
|
- return None
|
|
|
- arg = cmd_args[1]
|
|
|
- if is_int(arg) and int(arg) > 0:
|
|
|
- return int(arg)
|
|
|
try:
|
|
|
- return open(arg)
|
|
|
+ a,b = arg1.split(':')
|
|
|
except:
|
|
|
- die(1,m)
|
|
|
+ die(1,'First argument must be a generator backend number or two colon-separated arguments')
|
|
|
+
|
|
|
+ if not is_int(a) and a != 'all':
|
|
|
+ die(1,"First part of first argument must be a generator backend number or 'all'")
|
|
|
+
|
|
|
+ if is_int(b):
|
|
|
+ if opt.all_coins:
|
|
|
+ die(1,'--all-coins must be used with external tool only')
|
|
|
+ else:
|
|
|
+ proto = init_proto_from_opts()
|
|
|
+ ext_progs = list(ci.external_tests[proto.network]) + ['ext']
|
|
|
+ if b not in ext_progs:
|
|
|
+ die(1,f'Second part of first argument must be a generator backend number or one of {ext_progs}')
|
|
|
+
|
|
|
+ return pa( test='ab', gen_num=a, rounds=int(arg2), arg=b )
|
|
|
+
|
|
|
+def main():
|
|
|
|
|
|
-# begin execution
|
|
|
-from mmgen.protocol import init_proto
|
|
|
+ pa = parse_args()
|
|
|
+ proto = init_proto_from_opts()
|
|
|
+ addr_type = MMGenAddrType( proto=proto, id_str=opt.type or proto.dfl_mmtype )
|
|
|
+
|
|
|
+ if pa.test == 'ab':
|
|
|
+ protos = get_protos(proto,addr_type,pa.arg) if opt.all_coins else [proto]
|
|
|
+ for proto in protos:
|
|
|
+ ab_test( proto, pa.gen_num, pa.rounds, toolname_or_gen2_num=pa.arg )
|
|
|
+ else:
|
|
|
+ kg = KeyGenerator( proto, addr_type, pa.gen_num )
|
|
|
+ ag = AddrGenerator( proto, addr_type )
|
|
|
+ if pa.test == 'speed':
|
|
|
+ speed_test( proto, addr_type, kg, ag, pa.rounds )
|
|
|
+ elif pa.test == 'dump':
|
|
|
+ dump_test( proto, kg, ag, filename=pa.arg )
|
|
|
+
|
|
|
+ if saved_results:
|
|
|
+ import json
|
|
|
+ with open(results_file,'w') as fp:
|
|
|
+ fp.write(json.dumps( saved_results, indent=4 ))
|
|
|
+
|
|
|
+from subprocess import run,PIPE,DEVNULL
|
|
|
+from collections import namedtuple
|
|
|
+from mmgen.protocol import init_proto,init_proto_from_opts,CoinProtocol,init_genonly_altcoins
|
|
|
from mmgen.altcoin import CoinInfo as ci
|
|
|
from mmgen.key import PrivKey
|
|
|
from mmgen.addr import KeyGenerator,AddrGenerator,MMGenAddrType
|
|
|
|
|
|
-addr_type = MMGenAddrType(
|
|
|
- proto = proto,
|
|
|
- id_str = opt.type or proto.dfl_mmtype )
|
|
|
-
|
|
|
-ext_progs = list(ci.external_tests[proto.network])
|
|
|
-
|
|
|
-arg1 = cmd_args[0].split(':')
|
|
|
-
|
|
|
-if len(arg1) == 1:
|
|
|
- a,a_num = parse_arg1(arg1[0],'a')
|
|
|
- b = None
|
|
|
-elif len(arg1) == 2:
|
|
|
- a,a_num = parse_arg1(arg1[0],'a')
|
|
|
- b = parse_arg1(arg1[1],'b')
|
|
|
-else:
|
|
|
- opts.usage()
|
|
|
-
|
|
|
-if type(a) == type(b):
|
|
|
- rdie(1,'Address generators are the same!')
|
|
|
-
|
|
|
-arg2 = parse_arg2()
|
|
|
-
|
|
|
-if not opt.all:
|
|
|
- ag = AddrGenerator(proto,addr_type)
|
|
|
-
|
|
|
-if not b and type(arg2) == int:
|
|
|
- speed_test(a,ag,arg2)
|
|
|
-elif not b and hasattr(arg2,'read'):
|
|
|
- dump_test(a,ag,arg2)
|
|
|
-elif a and b and type(arg2) == int:
|
|
|
- if opt.all:
|
|
|
- from mmgen.protocol import CoinProtocol,init_genonly_altcoins
|
|
|
- init_genonly_altcoins(testnet=proto.testnet)
|
|
|
- for coin in ci.external_tests[proto.network][b.desc]:
|
|
|
- if coin.lower() not in CoinProtocol.coins:
|
|
|
-# ymsg(f'Coin {coin} not configured')
|
|
|
- continue
|
|
|
- proto = init_proto(coin)
|
|
|
- if addr_type not in proto.mmtypes:
|
|
|
- continue
|
|
|
- # proto has changed, so reinit kg and ag
|
|
|
- a = KeyGenerator(proto,addr_type,a_num)
|
|
|
- ag = AddrGenerator(proto,addr_type)
|
|
|
- b_chk = ci.get_test_support(proto.coin,addr_type.name,proto.network,tool=b.desc,verbose=not opt.quiet)
|
|
|
- if b_chk == b.desc:
|
|
|
- gentool_test(a,b,ag,arg2)
|
|
|
- else:
|
|
|
- gentool_test(a,b,ag,arg2)
|
|
|
-else:
|
|
|
- opts.usage()
|
|
|
+sys.argv = [sys.argv[0]] + ['--skip-cfg-file'] + sys.argv[1:]
|
|
|
+cmd_args = opts.init(opts_data,add_opts=['exact_output'])
|
|
|
+
|
|
|
+main()
|