From 545406aea404fb85f9d89b696b377c9f643be087 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Sat, 15 Jan 2022 14:00:08 +0000 Subject: [PATCH] addr.py: move PubKey and PrivKey to key.py --- mmgen/addr.py | 1 + mmgen/addrfile.py | 1 + mmgen/addrlist.py | 3 +- mmgen/key.py | 113 ++++++++++++++++++++++++++++ mmgen/obj.py | 85 --------------------- mmgen/passwdlist.py | 3 +- mmgen/protocol.py | 5 +- mmgen/tool.py | 1 + test/gentest.py | 3 +- test/misc/tool_api_test.py | 3 +- test/objattrtest_py_d/oat_common.py | 1 + test/objtest.py | 1 + test/objtest_py_d/ot_btc_mainnet.py | 1 + test/test_py_d/ts_main.py | 4 +- test/tooltest.py | 3 +- test/tooltest2.py | 3 +- 16 files changed, 134 insertions(+), 97 deletions(-) create mode 100755 mmgen/key.py diff --git a/mmgen/addr.py b/mmgen/addr.py index 3d61e730..7d9e23e0 100755 --- a/mmgen/addr.py +++ b/mmgen/addr.py @@ -26,6 +26,7 @@ from .objmethods import MMGenObject from .obj import * from .baseconv import * from .protocol import hash160 +from .key import PrivKey,PubKey class AddrGenerator(MMGenObject): def __new__(cls,proto,addr_type): diff --git a/mmgen/addrfile.py b/mmgen/addrfile.py index 36512a67..ad82e47d 100755 --- a/mmgen/addrfile.py +++ b/mmgen/addrfile.py @@ -33,6 +33,7 @@ from .util import ( from .protocol import init_proto from .obj import * from .seed import SeedID,is_seed_id +from .key import PrivKey from .addrlist import KeyList,AddrListData,dmsg_sc from .passwdlist import PasswordList diff --git a/mmgen/addrlist.py b/mmgen/addrlist.py index 1927aa3b..db29981a 100755 --- a/mmgen/addrlist.py +++ b/mmgen/addrlist.py @@ -23,8 +23,9 @@ addrlist.py: Address list classes for the MMGen suite from hashlib import sha256,sha512 from .util import qmsg,qmsg_r,suf,make_chksum_N,Msg from .objmethods import MMGenObject,Hilite,InitErrors -from .obj import MMGenListItem,ListItemAttr,MMGenDict,WalletPassword,PrivKey +from .obj import MMGenListItem,ListItemAttr,MMGenDict,WalletPassword from .seed import SeedID +from .key import PrivKey from .obj import MMGenID,MMGenAddrType,CoinAddr,AddrIdx,AddrListID,ViewKey def dmsg_sc(desc,data): diff --git a/mmgen/key.py b/mmgen/key.py new file mode 100755 index 00000000..0577eb6b --- /dev/null +++ b/mmgen/key.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution +# Copyright (C)2013-2022 The MMGen Project +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +""" +key.py: MMGen public and private key objects +""" + +from string import ascii_letters,digits +from .objmethods import Hilite,InitErrors,MMGenObject +from .obj import ImmutableAttr,get_obj,HexStr + +class WifKey(str,Hilite,InitErrors): + """ + Initialize a WIF key, checking its well-formedness. + The numeric validity of the private key it encodes is not checked. + """ + width = 53 + color = 'blue' + def __new__(cls,proto,wif): + if type(wif) == cls: + return wif + try: + assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string' + proto.parse_wif(wif) # raises exception on error + return str.__new__(cls,wif) + except Exception as e: + return cls.init_fail(e,wif) + +def is_wif(proto,s): + return get_obj( WifKey, proto=proto, wif=s, silent=True, return_bool=True ) + +class PubKey(HexStr,MMGenObject): # TODO: add some real checks + + def __new__(cls,s,privkey): + try: + me = HexStr.__new__(cls,s,case='lower') + me.privkey = privkey + me.compressed = privkey.compressed + return me + except Exception as e: + return cls.init_fail(e,s) + +class PrivKey(str,Hilite,InitErrors,MMGenObject): + """ + Input: a) raw, non-preprocessed bytes; or b) WIF key. + Output: preprocessed hexadecimal key, plus WIF key in 'wif' attribute + For coins without a WIF format, 'wif' contains the preprocessed hex. + The numeric validity of the resulting key is always checked. + """ + color = 'red' + width = 64 + trunc_ok = False + + compressed = ImmutableAttr(bool,typeconv=False) + wif = ImmutableAttr(WifKey,typeconv=False) + + # initialize with (priv_bin,compressed), WIF or self + def __new__(cls,proto,s=None,compressed=None,wif=None,pubkey_type=None): + if type(s) == cls: + return s + if wif: + try: + assert s == None,"'wif' and key hex args are mutually exclusive" + assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string' + k = proto.parse_wif(wif) # raises exception on error + me = str.__new__(cls,k.sec.hex()) + me.compressed = k.compressed + me.pubkey_type = k.pubkey_type + me.wif = str.__new__(WifKey,wif) # check has been done + me.orig_hex = None + if k.sec != proto.preprocess_key(k.sec,k.pubkey_type): + from .exception import PrivateKeyError + raise PrivateKeyError( + f'{proto.cls_name} WIF key {me.wif!r} encodes private key with invalid value {me}') + me.proto = proto + return me + except Exception as e: + return cls.init_fail(e,s,objname=f'{proto.coin} WIF key') + else: + try: + assert s,'private key bytes data missing' + assert pubkey_type is not None,"'pubkey_type' arg missing" + assert len(s) == cls.width // 2, f'key length must be {cls.width // 2} bytes' + if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds + me = str.__new__(cls,s.hex()) + else: + assert compressed is not None, "'compressed' arg missing" + assert type(compressed) == bool,( + f"'compressed' must be of type bool, not {type(compressed).__name__}" ) + me = str.__new__(cls,proto.preprocess_key(s,pubkey_type).hex()) + me.wif = WifKey(proto,proto.hex2wif(me,pubkey_type,compressed)) + me.compressed = compressed + me.pubkey_type = pubkey_type + me.orig_hex = s.hex() # save the non-preprocessed key + me.proto = proto + return me + except Exception as e: + return cls.init_fail(e,s) diff --git a/mmgen/obj.py b/mmgen/obj.py index d340a13b..9287a7c3 100755 --- a/mmgen/obj.py +++ b/mmgen/obj.py @@ -62,8 +62,6 @@ def is_addrlist_id(s): return get_obj(AddrListID, sid=s, silent=True,return_ def is_mmgen_id(proto,s): return get_obj(MMGenID, proto=proto, id_str=s, silent=True,return_bool=True) def is_coin_addr(proto,s): return get_obj(CoinAddr, proto=proto, addr=s, silent=True,return_bool=True) -def is_wif(proto,s): return get_obj(WifKey, proto=proto, wif=s, silent=True,return_bool=True) - # dict that keeps a list of keys for efficient lookup by index class IndexedDict(dict): @@ -435,89 +433,6 @@ class WalletPassword(HexStr): color,width,hexcase = 'blue',32,'lower' class MoneroViewKey(HexStr): color,width,hexcase = 'cyan',64,'lower' # FIXME - no checking performed class MMGenTxID(HexStr): color,width,hexcase = 'red',6,'upper' -class WifKey(str,Hilite,InitErrors): - """ - Initialize a WIF key, checking its well-formedness. - The numeric validity of the private key it encodes is not checked. - """ - width = 53 - color = 'blue' - def __new__(cls,proto,wif): - if type(wif) == cls: - return wif - try: - assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string' - proto.parse_wif(wif) # raises exception on error - return str.__new__(cls,wif) - except Exception as e: - return cls.init_fail(e,wif) - -class PubKey(HexStr,MMGenObject): # TODO: add some real checks - def __new__(cls,s,privkey): - try: - me = HexStr.__new__(cls,s,case='lower') - me.privkey = privkey - me.compressed = privkey.compressed - return me - except Exception as e: - return cls.init_fail(e,s) - -class PrivKey(str,Hilite,InitErrors,MMGenObject): - """ - Input: a) raw, non-preprocessed bytes; or b) WIF key. - Output: preprocessed hexadecimal key, plus WIF key in 'wif' attribute - For coins without a WIF format, 'wif' contains the preprocessed hex. - The numeric validity of the resulting key is always checked. - """ - color = 'red' - width = 64 - trunc_ok = False - - compressed = ImmutableAttr(bool,typeconv=False) - wif = ImmutableAttr(WifKey,typeconv=False) - - # initialize with (priv_bin,compressed), WIF or self - def __new__(cls,proto,s=None,compressed=None,wif=None,pubkey_type=None): - if type(s) == cls: - return s - if wif: - try: - assert s == None,"'wif' and key hex args are mutually exclusive" - assert set(wif) <= set(ascii_letters+digits),'not an ascii alphanumeric string' - k = proto.parse_wif(wif) # raises exception on error - me = str.__new__(cls,k.sec.hex()) - me.compressed = k.compressed - me.pubkey_type = k.pubkey_type - me.wif = str.__new__(WifKey,wif) # check has been done - me.orig_hex = None - if k.sec != proto.preprocess_key(k.sec,k.pubkey_type): - raise PrivateKeyError( - f'{proto.cls_name} WIF key {me.wif!r} encodes private key with invalid value {me}') - me.proto = proto - return me - except Exception as e: - return cls.init_fail(e,s,objname=f'{proto.coin} WIF key') - else: - try: - assert s,'private key bytes data missing' - assert pubkey_type is not None,"'pubkey_type' arg missing" - assert len(s) == cls.width // 2, f'key length must be {cls.width // 2} bytes' - if pubkey_type == 'password': # skip WIF creation and pre-processing for passwds - me = str.__new__(cls,s.hex()) - else: - assert compressed is not None, "'compressed' arg missing" - assert type(compressed) == bool,( - f"'compressed' must be of type bool, not {type(compressed).__name__}" ) - me = str.__new__(cls,proto.preprocess_key(s,pubkey_type).hex()) - me.wif = WifKey(proto,proto.hex2wif(me,pubkey_type,compressed)) - me.compressed = compressed - me.pubkey_type = pubkey_type - me.orig_hex = s.hex() # save the non-preprocessed key - me.proto = proto - return me - except Exception as e: - return cls.init_fail(e,s) - class AddrListID(str,Hilite,InitErrors,MMGenObject): width = 10 trunc_ok = False diff --git a/mmgen/passwdlist.py b/mmgen/passwdlist.py index 8cce925f..5a8a4b3f 100755 --- a/mmgen/passwdlist.py +++ b/mmgen/passwdlist.py @@ -24,8 +24,9 @@ from collections import namedtuple from .exception import InvalidPasswdFormat from .util import ymsg,is_hex_str,is_int,keypress_confirm -from .obj import ImmutableAttr,ListItemAttr,MMGenPWIDString,PrivKey +from .obj import ImmutableAttr,ListItemAttr,MMGenPWIDString from .baseconv import baseconv,is_b32_str,is_b58_str +from .key import PrivKey from .addr import MMGenPasswordType,AddrIdx,AddrListID,is_xmrseed,is_bip39_str from .addrlist import ( AddrListChksum, diff --git a/mmgen/protocol.py b/mmgen/protocol.py index e30f45f4..db55f584 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -25,7 +25,7 @@ from collections import namedtuple from .util import msg,ymsg,Msg,ydie from .devtools import * -from .obj import CoinAddr,MMGenAddrType,PrivKey +from .obj import CoinAddr,MMGenAddrType from .globalvars import g from .amt import BTCAmt,LTCAmt,BCHAmt,B2XAmt,XMRAmt from .altcoins.eth.obj import ETHAmt @@ -178,9 +178,6 @@ class CoinProtocol(MMGenObject): def addr_type(self,id_str): return MMGenAddrType( proto=self, id_str=id_str ) - def priv_key(self,s): - return PrivKey( proto=self, s=s ) - class Secp256k1(Base): """ Bitcoin and Ethereum protocols inherit from this class diff --git a/mmgen/tool.py b/mmgen/tool.py index 38b7b932..b9a53acc 100755 --- a/mmgen/tool.py +++ b/mmgen/tool.py @@ -23,6 +23,7 @@ tool.py: Routines for the 'mmgen-tool' utility from .protocol import hash160 from .common import * from .crypto import * +from .key import PrivKey from .seedsplit import MasterShareIdx from .addr import * from .addrlist import AddrList,KeyAddrList diff --git a/test/gentest.py b/test/gentest.py index 481c5b46..dd1e3ab8 100755 --- a/test/gentest.py +++ b/test/gentest.py @@ -401,7 +401,8 @@ def parse_arg2(): # begin execution from mmgen.protocol import init_proto from mmgen.altcoin import CoinInfo as ci -from mmgen.obj import MMGenAddrType,PrivKey +from mmgen.obj import MMGenAddrType +from mmgen.key import PrivKey from mmgen.addr import KeyGenerator,AddrGenerator addr_type = MMGenAddrType( diff --git a/test/misc/tool_api_test.py b/test/misc/tool_api_test.py index 17a22f1f..66db0830 100755 --- a/test/misc/tool_api_test.py +++ b/test/misc/tool_api_test.py @@ -9,7 +9,8 @@ tool_api_test.py: test the MMGen suite tool API import sys,os from mmgen.common import * -from mmgen.obj import PrivKey,CoinAddr +from mmgen.key import PrivKey +from mmgen.addr import CoinAddr keys = [ '118089d66b4a5853765e94923abdd5de4616c6e5118089d66b4a5853765e9492', diff --git a/test/objattrtest_py_d/oat_common.py b/test/objattrtest_py_d/oat_common.py index 2f3447d8..35e9827d 100755 --- a/test/objattrtest_py_d/oat_common.py +++ b/test/objattrtest_py_d/oat_common.py @@ -16,6 +16,7 @@ from mmgen.protocol import * from mmgen.addr import * from mmgen.tx import * from mmgen.tw import * +from mmgen.key import * from ..include.common import getrand from collections import namedtuple diff --git a/test/objtest.py b/test/objtest.py index 89b5b2d6..ccb17ae7 100755 --- a/test/objtest.py +++ b/test/objtest.py @@ -37,6 +37,7 @@ from mmgen.addr import * from mmgen.addrlist import * from mmgen.addrdata import * from mmgen.amt import * +from mmgen.key import * opts_data = { 'sets': [('super_silent', True, 'silent', True)], diff --git a/test/objtest_py_d/ot_btc_mainnet.py b/test/objtest_py_d/ot_btc_mainnet.py index 6bb58246..f6fd0f3d 100755 --- a/test/objtest_py_d/ot_btc_mainnet.py +++ b/test/objtest_py_d/ot_btc_mainnet.py @@ -10,6 +10,7 @@ test.objtest_py_d.ot_btc_mainnet: BTC mainnet test vectors for MMGen data object from mmgen.obj import * from mmgen.addrlist import AddrIdxList from mmgen.seedsplit import * +from mmgen.key import * from .ot_common import * from mmgen.protocol import init_proto diff --git a/test/test_py_d/ts_main.py b/test/test_py_d/ts_main.py index 8b467960..3b470bb7 100755 --- a/test/test_py_d/ts_main.py +++ b/test/test_py_d/ts_main.py @@ -374,7 +374,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared): out.append(self._create_fake_unspent_entry(coinaddr,d['al_id'],idx,lbl,segwit=d['segwit'])) if non_mmgen_input: - from mmgen.obj import PrivKey + from mmgen.key import PrivKey privkey = PrivKey( self.proto, getrand(32), @@ -417,7 +417,7 @@ class TestSuiteMain(TestSuiteBase,TestSuiteShared): return ad,tx_data def _make_txcreate_cmdline(self,tx_data): - from mmgen.obj import PrivKey + from mmgen.key import PrivKey privkey = PrivKey(self.proto,getrand(32),compressed=True,pubkey_type='std') t = ('compressed','segwit')['S' in self.proto.mmtypes] from mmgen.addr import AddrGenerator,KeyGenerator diff --git a/test/tooltest.py b/test/tooltest.py index 0f0c89eb..82fa8ea6 100755 --- a/test/tooltest.py +++ b/test/tooltest.py @@ -185,7 +185,8 @@ if opt.list_names: ) die(0,'\n{}\n {}'.format(yellow('Untested commands:'),'\n '.join(uc))) -from mmgen.tx import is_wif,is_coin_addr +from mmgen.key import is_wif +from mmgen.tx import is_coin_addr def is_wif_loc(s): return is_wif(proto,s) def is_coin_addr_loc(s): diff --git a/test/tooltest2.py b/test/tooltest2.py index bb0b9de8..f504a092 100755 --- a/test/tooltest2.py +++ b/test/tooltest2.py @@ -44,7 +44,8 @@ NL = ('\n','\r\n')[g.platform=='win'] def is_str(s): return type(s) == str -from mmgen.obj import is_wif,is_coin_addr +from mmgen.key import is_wif +from mmgen.obj import is_coin_addr def is_wif_loc(s): return is_wif(proto,s) def is_coin_addr_loc(s):