From 7412bf56ef8f0851169dd95e4c1cd68d6e65eee6 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Mon, 8 Jun 2020 17:03:21 +0000 Subject: [PATCH] tool.py: fix tool API; test.py: add tool_api test, minor fixes --- mmgen/tool.py | 29 +++++------ test/misc/tool_api_test.py | 82 +++++++++++++++++++++++++++++++ test/ref/bad-comment-unspent.json | 20 +++++--- test/test_py_d/ts_main.py | 4 +- test/test_py_d/ts_tool.py | 7 +++ 5 files changed, 118 insertions(+), 24 deletions(-) create mode 100755 test/misc/tool_api_test.py diff --git a/mmgen/tool.py b/mmgen/tool.py index 721d7937..c1c9d21a 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -273,7 +273,7 @@ class MMGenToolCmds(metaclass=MMGenToolCmdMeta): def __init__(self,proto=None,mmtype=None): from .protocol import init_proto_from_opts self.proto = proto or init_proto_from_opts() - self.mmtype = mmtype or getattr(opt,'type',None) or self.proto.dfl_mmtype + self.mmtype = MMGenAddrType(self.proto,(mmtype or getattr(opt,'type',None) or self.proto.dfl_mmtype)) if g.token: self.proto.tokensym = g.token.upper() @@ -470,7 +470,7 @@ class MMGenToolCmdCoin(MMGenToolCmds): def wif2redeem_script(self,wifkey:'sstr'): # new "convert a WIF private key to a Segwit P2SH-P2WPKH redeem script" - assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit' + assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit' gd = self.init_generators() privhex = PrivKey( self.proto, @@ -479,7 +479,7 @@ class MMGenToolCmdCoin(MMGenToolCmds): def wif2segwit_pair(self,wifkey:'sstr'): "generate both a Segwit P2SH-P2WPKH redeem script and address from WIF" - assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit' + assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit' gd = self.init_generators() pubhex = gd.kg.to_pubhex(PrivKey( self.proto, @@ -505,26 +505,26 @@ class MMGenToolCmdCoin(MMGenToolCmds): def pubhex2addr(self,pubkeyhex:'sstr'): "convert a hex pubkey to an address" - if self.mmtype == 'segwit': + if self.mmtype.name == 'segwit': return self.proto.pubhex2segwitaddr(pubkeyhex) else: return self.pubhash2addr(hash160(pubkeyhex)) def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new "convert a hex pubkey to a Segwit P2SH-P2WPKH redeem script" - assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit' + assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit' return self.proto.pubhex2redeem_script(pubkeyhex) def redeem_script2addr(self,redeem_scripthex:'sstr'): # new "convert a Segwit P2SH-P2WPKH redeem script to an address" - assert self.mmtype == 'segwit','This command is meaningful only for --type=segwit' + assert self.mmtype.name == 'segwit','This command is meaningful only for --type=segwit' assert redeem_scripthex[:4] == '0014','{!r}: invalid redeem script'.format(redeem_scripthex) assert len(redeem_scripthex) == 44,'{} bytes: invalid redeem script length'.format(len(redeem_scripthex)//2) return self.pubhash2addr(hash160(redeem_scripthex)) def pubhash2addr(self,pubhashhex:'sstr'): "convert public key hash to address" - if self.mmtype == 'bech32': + if self.mmtype.name == 'bech32': return self.proto.pubhash2bech32addr(pubhashhex) else: gd = self.init_generators('addrtype_only') @@ -1222,10 +1222,11 @@ class tool_api( """ Initializer - takes no arguments """ - super().__init__() + import mmgen.opts + opts.UserOpts._reset_ok += ('usr_randchars',) if not hasattr(opt,'version'): - opts.init() - self.mmtype = self.proto.dfl_mmtype + opts.init(add_opts=['use_old_ed25519']) + super().__init__() def init_coin(self,coinsym,network): """ @@ -1236,7 +1237,7 @@ class tool_api( from .protocol import init_proto,init_genonly_altcoins altcoin_trust_level = init_genonly_altcoins(coinsym,testnet=network in ('testnet','regtest')) warn_altcoins(coinsym,altcoin_trust_level) - self.proto = init_proto(coinsym,network=network) # FIXME + self.proto = init_proto(coinsym,network=network) return self.proto @property @@ -1265,14 +1266,14 @@ class tool_api( The available address types for current coin/network pair. The first-listed is the default """ - return [MMGenAddrType(proto=proto,id_str=id_str).name for id_str in self.proto.mmtypes] + return [MMGenAddrType(proto=self.proto,id_str=id_str).name for id_str in self.proto.mmtypes] def print_addrtypes(self): """ Print the available address types for current coin/network pair along with a description. The first-listed is the default """ - for t in [MMGenAddrType(proto=proto,id_str=id_str).name for id_str in self.proto.mmtypes]: + for t in [MMGenAddrType(proto=self.proto,id_str=id_str) for id_str in self.proto.mmtypes]: print('{:<12} - {}'.format(t.name,t.desc)) @property @@ -1282,7 +1283,7 @@ class tool_api( @addrtype.setter def addrtype(self,val): - self.mmtype = val + self.mmtype = MMGenAddrType(self.proto,val) @property def usr_randchars(self): diff --git a/test/misc/tool_api_test.py b/test/misc/tool_api_test.py new file mode 100755 index 00000000..3b5d9486 --- /dev/null +++ b/test/misc/tool_api_test.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2020 The MMGen Project + +""" +tool_api_test.py: test the MMGen suite tool API +""" + +import sys,os +os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(os.path.dirname(sys.argv[0]))))) +sys.path[0] = os.curdir + +def check_equal(a,b): + assert a == b, f'{a} != {b}' + +def msg(*args,**kwargs): + print(*args,**kwargs,file=sys.stdout) + +def init_coin(t,coinsym,network,addrtype,triplet): + t.init_coin(coinsym,network) + t.addrtype = addrtype + check_equal(type(t.addrtype).__name__,'MMGenAddrType') + check_equal(f'{t.coin} {t.proto.cls_name} {t.addrtype}', triplet) + msg('\ncoin/proto/type:',triplet) + +def run_test(): + key_bytes = bytes.fromhex('deadbeef' * 8) + + from mmgen.tool import tool_api + tool = tool_api() + + tool.coins + tool.print_addrtypes() + + check_equal(f'{tool.coin} {tool.proto.cls_name} {tool.addrtype}', 'BTC Bitcoin L' ) + + tool.usr_randchars # getter + tool.usr_randchars = 0 # setter + check_equal(tool.usr_randchars,0) + + init_coin(tool,'xmr','mainnet','M','XMR Monero M') + msg('\n'.join(tool.randpair())) + + init_coin(tool,'etc','mainnet','E','ETC EthereumClassic E') + msg('\n'.join(tool.randpair())) + + init_coin(tool,'ltc','regtest','bech32','LTC LitecoinRegtest B') + + wif,addr = tool.randpair() + from mmgen.obj import PrivKey,CoinAddr + msg('wif:',PrivKey(proto=tool.proto,wif=wif).wif) + msg('addr:',CoinAddr(proto=tool.proto,addr=addr)) + + wif = PrivKey(proto=tool.proto,s=key_bytes,compressed=True,pubkey_type='std').wif + addr = tool.wif2addr(wif) + msg('wif:',PrivKey(proto=tool.proto,wif=wif).wif) + msg('addr:',CoinAddr(proto=tool.proto,addr=addr)) + + addr_chk = tool.privhex2addr(key_bytes.hex()) + + check_equal(addr,addr_chk) + check_equal(wif,'cV3ZRqf8PhyfiFwtJfkvGu2qmBsazE1wXoA2A16S3nixb3BTvvVx') + check_equal(addr,'rltc1qvmqas4maw7lg9clqu6kqu9zq9cluvllnz4kj9y') + + init_coin(tool,'zec','mainnet','Z','ZEC Zcash Z') + + wif = PrivKey(proto=tool.proto,s=key_bytes,compressed=True,pubkey_type='zcash_z').wif + addr = tool.wif2addr(wif) + msg('wif:',wif) + msg('addr:',CoinAddr(proto=tool.proto,addr=addr)) + + addr_chk = tool.privhex2addr(key_bytes.hex()) + wif_chk = PrivKey(proto=tool.proto,wif=wif).wif + + check_equal(addr,addr_chk) + check_equal(wif,wif_chk) + check_equal( + addr, + 'zchFELwBxqsAubsLQ8yZgPCDDGukjXJssgCbiTPwFNmFwn9haLnDatzfhLdZzJT4PcU4o2yr92B52UFirUzEdF6ZYM2gBkM' ) + +run_test() diff --git a/test/ref/bad-comment-unspent.json b/test/ref/bad-comment-unspent.json index 9e7e608a..e1020c54 100644 --- a/test/ref/bad-comment-unspent.json +++ b/test/ref/bad-comment-unspent.json @@ -1,8 +1,12 @@ -[{'address': '153AwzcymCgiy7dBxHkpeSn8yzbfnRvVPx', - 'amount': BTCAmt('34.21677044'), - 'confirmations': 7528082, - 'label': '96325D83:L:99 This label has a control character (tab) here: [ ]', - 'scriptPubKey': '76a9142c49a3ad8d89af713197e55190a7c3bc47e0208e88ac', - 'spendable': False, - 'txid': '2588cbbea1f3413ee4556c0f12f825f8670771862864a44f5ad39a71e1afdb13', - 'vout': 3}] +[ + { + "address": "153AwzcymCgiy7dBxHkpeSn8yzbfnRvVPx", + "amount": "34.21677044", + "confirmations": 7528082, + "label": "96325D83:L:99 This label has a control character (tab) here: [\t]", + "scriptPubKey": "76a9142c49a3ad8d89af713197e55190a7c3bc47e0208e88ac", + "spendable": false, + "txid": "2588cbbea1f3413ee4556c0f12f825f8670771862864a44f5ad39a71e1afdb13", + "vout": 3 + } +] diff --git a/test/test_py_d/ts_main.py b/test/test_py_d/ts_main.py index 7f46cac5..e962be28 100755 --- a/test/test_py_d/ts_main.py +++ b/test/test_py_d/ts_main.py @@ -223,7 +223,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared): t.license() wcls = MMGenWallet t.passphrase(wcls.desc,self.cfgs['1']['wpasswd']) - t.expect('Generating subseed.*10S',regex=True) + t.expect(r'Generating subseed.*\D10S',regex=True) t.passphrase_new('new '+wcls.desc,'foo') t.usr_rand(self.usr_rand_chars) fn = t.written_to_file(capfirst(wcls.desc)) @@ -238,7 +238,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared): t = self.spawn('mmgen-subwalletgen', args) t.license() t.passphrase(icls.desc,self.cfgs['1']['wpasswd']) - t.expect('Generating subseed.*3L',regex=True) + t.expect(r'Generating subseed.*\D3L',regex=True) fn = t.written_to_file(capfirst(ocls.desc)) ext = get_extension(fn) assert ext == ocls.ext,'incorrect file extension: {}'.format(ext) diff --git a/test/test_py_d/ts_tool.py b/test/test_py_d/ts_tool.py index c0fcf410..f392b8ec 100755 --- a/test/test_py_d/ts_tool.py +++ b/test/test_py_d/ts_tool.py @@ -26,6 +26,7 @@ class TestSuiteTool(TestSuiteMain,TestSuiteBase): ('tool_encrypt', (9,"'mmgen-tool encrypt' (random data)", [])), ('tool_decrypt', (9,"'mmgen-tool decrypt' (random data)", [[[enc_infn+'.mmenc'],9]])), ('tool_twview_bad_comment',(9,"'mmgen-tool twview' (with bad comment)", [])), + ('tool_api', (9,'tool API (initialization, config methods)',[])), # ('tool_encrypt_ref', (9,"'mmgen-tool encrypt' (reference text)", [])), ) @@ -80,3 +81,9 @@ class TestSuiteTool(TestSuiteMain,TestSuiteBase): t.read() t.req_exit_val = 2 return t + + def tool_api(self): + t = self.spawn('tool_api_test.py',cmd_dir='test/misc') + t.expect('legacy.*compressed.*segwit.*bech32',regex=True) + t.read() + return t