From 362d8cfeedc74750a099a54c2362def5654afd25 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Thu, 3 Feb 2022 20:40:40 +0000 Subject: [PATCH] add proto.common module --- mmgen/addrgen.py | 6 +++--- mmgen/addrlist.py | 2 +- mmgen/altcoin.py | 4 ++-- mmgen/proto/btc.py | 42 ++++++-------------------------------- mmgen/proto/common.py | 47 +++++++++++++++++++++++++++++++++++++++++++ mmgen/protocol.py | 2 -- mmgen/tool/coin.py | 4 ++-- mmgen/tool/util.py | 10 ++++----- mmgen/xmrwallet.py | 5 +++-- 9 files changed, 69 insertions(+), 53 deletions(-) create mode 100755 mmgen/proto/common.py diff --git a/mmgen/addrgen.py b/mmgen/addrgen.py index 550276c4..3ad5219a 100755 --- a/mmgen/addrgen.py +++ b/mmgen/addrgen.py @@ -20,7 +20,7 @@ addrgen.py: Address and view key generation classes for the MMGen suite """ -from .proto.btc import hash160,_b58chk_encode +from .proto.common import hash160,b58chk_encode from .addr import CoinAddr,MMGenAddrType,MoneroViewKey,ZcashViewKey # decorator for to_addr() and to_viewkey() @@ -118,14 +118,14 @@ class addr_generator: @check_data def to_addr(self,data): - ret = _b58chk_encode( + ret = b58chk_encode( self.proto.addr_fmt_to_ver_bytes('zcash_z') + data.pubkey ) return CoinAddr( self.proto, ret ) @check_data def to_viewkey(self,data): - ret = _b58chk_encode( + ret = b58chk_encode( self.proto.addr_fmt_to_ver_bytes('viewkey') + data.viewkey_bytes ) return ZcashViewKey( self.proto, ret ) diff --git a/mmgen/addrlist.py b/mmgen/addrlist.py index 3819eb7c..8e5eb745 100755 --- a/mmgen/addrlist.py +++ b/mmgen/addrlist.py @@ -281,7 +281,7 @@ class AddrList(MMGenObject): # Address info for a single seed ID return out def gen_wallet_passwd(self,privbytes): - from .proto.btc import hash256 + from .proto.common import hash256 return WalletPassword( hash256(privbytes)[:16].hex() ) def check_format(self,addr): diff --git a/mmgen/altcoin.py b/mmgen/altcoin.py index 4ca26f58..adcc5688 100755 --- a/mmgen/altcoin.py +++ b/mmgen/altcoin.py @@ -556,10 +556,10 @@ class CoinInfo(object): return '1' def phash2addr(ver_num,pk_hash): - from .proto.btc import _b58chk_encode + from .proto.common import b58chk_encode bl = ver_num.bit_length() ver_bytes = int.to_bytes(ver_num,bl//8 + bool(bl%8),'big') - return _b58chk_encode(ver_bytes + pk_hash) + return b58chk_encode(ver_bytes + pk_hash) low = phash2addr(ver_num,b'\x00'*20) high = phash2addr(ver_num,b'\xff'*20) diff --git a/mmgen/proto/btc.py b/mmgen/proto/btc.py index ed1f7246..4ace3266 100755 --- a/mmgen/proto/btc.py +++ b/mmgen/proto/btc.py @@ -12,38 +12,8 @@ Bitcoin protocol """ -from ..protocol import CoinProtocol,parsed_wif,parsed_addr,_finfo,_b58a,_nw -import hashlib - -def hash160(in_bytes): # OP_HASH160 - return hashlib.new('ripemd160',hashlib.sha256(in_bytes).digest()).digest() - -def hash256(in_bytes): # OP_HASH256 - return hashlib.sha256(hashlib.sha256(in_bytes).digest()).digest() - -# From en.bitcoin.it: -# The Base58 encoding used is home made, and has some differences. -# Especially, leading zeroes are kept as single zeroes when conversion happens. -# Test: 5JbQQTs3cnoYN9vDYaGY6nhQ1DggVsY4FJNBUfEfpSQqrEp3srk -# The 'zero address': -# 1111111111111111111114oLvT2 (pubkeyhash = '\0'*20) - -def _b58chk_encode(in_bytes): - lzeroes = len(in_bytes) - len(in_bytes.lstrip(b'\x00')) - def do_enc(n): - while n: - yield _b58a[n % 58] - n //= 58 - return ('1' * lzeroes) + ''.join(do_enc(int.from_bytes(in_bytes+hash256(in_bytes)[:4],'big')))[::-1] - -def _b58chk_decode(s): - lzeroes = len(s) - len(s.lstrip('1')) - res = sum(_b58a.index(ch) * 58**n for n,ch in enumerate(s[::-1])) - bl = res.bit_length() - out = b'\x00' * lzeroes + res.to_bytes(bl//8 + bool(bl%8),'big') - if out[-4:] != hash256(out[:-4])[:4]: - raise ValueError('_b58chk_decode(): incorrect checksum') - return out[:-4] +from ..protocol import CoinProtocol,parsed_wif,parsed_addr,_finfo,_nw +from .common import * class mainnet(CoinProtocol.Secp256k1): # chainparams.cpp """ @@ -83,13 +53,13 @@ class mainnet(CoinProtocol.Secp256k1): # chainparams.cpp def bytes2wif(self,privbytes,pubkey_type,compressed): # input is preprocessed hex assert len(privbytes) == self.privkey_len, f'{len(privbytes)} bytes: incorrect private key length!' assert pubkey_type in self.wif_ver_num, f'{pubkey_type!r}: invalid pubkey_type' - return _b58chk_encode( + return b58chk_encode( bytes.fromhex(self.wif_ver_num[pubkey_type]) + privbytes + (b'',b'\x01')[bool(compressed)]) def parse_wif(self,wif): - key = _b58chk_decode(wif) + key = b58chk_decode(wif) for k,v in self.wif_ver_num.items(): v = bytes.fromhex(v) @@ -126,11 +96,11 @@ class mainnet(CoinProtocol.Secp256k1): # chainparams.cpp return parsed_addr( bytes(ret[1]), 'bech32' ) if ret[1] else False - return self.parse_addr_bytes(_b58chk_decode(addr)) + return self.parse_addr_bytes(b58chk_decode(addr)) def pubhash2addr(self,pubkey_hash,p2sh): assert len(pubkey_hash) == 20, f'{len(pubkey_hash)}: invalid length for pubkey hash' - return _b58chk_encode( + return b58chk_encode( self.addr_fmt_to_ver_bytes(('p2pkh','p2sh')[p2sh],return_hex=False) + pubkey_hash ) diff --git a/mmgen/proto/common.py b/mmgen/proto/common.py new file mode 100755 index 00000000..6940312c --- /dev/null +++ b/mmgen/proto/common.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +# +# mmgen = Multi-Mode GENerator, a command-line cryptocurrency wallet +# Copyright (C)2013-2022 The MMGen Project +# Licensed under the GNU General Public License, Version 3: +# https://www.gnu.org/licenses +# Public project repositories: +# https://github.com/mmgen/mmgen +# https://gitlab.com/mmgen/mmgen + +""" +Functions and constants used by multiple protocols +""" + +import hashlib + +b58a = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' + +def hash160(in_bytes): # OP_HASH160 + return hashlib.new('ripemd160',hashlib.sha256(in_bytes).digest()).digest() + +def hash256(in_bytes): # OP_HASH256 + return hashlib.sha256(hashlib.sha256(in_bytes).digest()).digest() + +# From en.bitcoin.it: +# The Base58 encoding used is home made, and has some differences. +# Especially, leading zeroes are kept as single zeroes when conversion happens. +# Test: 5JbQQTs3cnoYN9vDYaGY6nhQ1DggVsY4FJNBUfEfpSQqrEp3srk +# The 'zero address': +# 1111111111111111111114oLvT2 (pubkeyhash = '\0'*20) + +def b58chk_encode(in_bytes): + lzeroes = len(in_bytes) - len(in_bytes.lstrip(b'\x00')) + def do_enc(n): + while n: + yield b58a[n % 58] + n //= 58 + return ('1' * lzeroes) + ''.join(do_enc(int.from_bytes(in_bytes+hash256(in_bytes)[:4],'big')))[::-1] + +def b58chk_decode(s): + lzeroes = len(s) - len(s.lstrip('1')) + res = sum(b58a.index(ch) * 58**n for n,ch in enumerate(s[::-1])) + bl = res.bit_length() + out = b'\x00' * lzeroes + res.to_bytes(bl//8 + bool(bl%8),'big') + if out[-4:] != hash256(out[:-4])[:4]: + raise ValueError('b58chk_decode(): incorrect checksum') + return out[:-4] diff --git a/mmgen/protocol.py b/mmgen/protocol.py index b49aabe0..60af7df7 100755 --- a/mmgen/protocol.py +++ b/mmgen/protocol.py @@ -31,8 +31,6 @@ parsed_addr = namedtuple('parsed_addr',['bytes','fmt']) _finfo = namedtuple('fork_info',['height','hash','name','replayable']) _nw = namedtuple('coin_networks',['mainnet','testnet','regtest']) -_b58a='123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz' # shared by Bitcoin and Monero - class CoinProtocol(MMGenObject): proto_info = namedtuple('proto_info',['name','trust_level']) # trust levels: see altcoin.py diff --git a/mmgen/tool/coin.py b/mmgen/tool/coin.py index d970ef4c..4f23a676 100755 --- a/mmgen/tool/coin.py +++ b/mmgen/tool/coin.py @@ -133,7 +133,7 @@ class tool_cmd(tool_cmd_base): if self.mmtype.name == 'segwit': return self.proto.pubkey2segwitaddr( pubkey ) else: - from ..proto.btc import hash160 + from ..proto.common import hash160 return self.pubhash2addr( hash160(pubkey).hex() ) def pubhex2redeem_script(self,pubkeyhex:'sstr'): # new @@ -146,7 +146,7 @@ class tool_cmd(tool_cmd_base): assert self.mmtype.name == 'segwit', 'This command is meaningful only for --type=segwit' assert redeem_scripthex[:4] == '0014', f'{redeem_scripthex!r}: invalid redeem script' assert len(redeem_scripthex) == 44, f'{len(redeem_scripthex)//2} bytes: invalid redeem script length' - from ..proto.btc import hash160 + from ..proto.common import hash160 return self.pubhash2addr( hash160(bytes.fromhex(redeem_scripthex)).hex() ) def pubhash2addr(self,pubhashhex:'sstr'): diff --git a/mmgen/tool/util.py b/mmgen/tool/util.py index e05ee4a2..96ffbbc2 100755 --- a/mmgen/tool/util.py +++ b/mmgen/tool/util.py @@ -78,7 +78,7 @@ class tool_cmd(tool_cmd_base): def hash160(self,hexstr:'sstr'): "compute ripemd160(sha256(data)) (convert hex pubkey to hex addr)" - from ..proto.btc import hash160 + from ..proto.common import hash160 return hash160( bytes.fromhex(hexstr) ).hex() def hash256(self,string_or_bytes:str,file_input=False,hex_input=False): # TODO: handle stdin @@ -143,13 +143,13 @@ class tool_cmd(tool_cmd_base): def hextob58chk(self,hexstr:'sstr'): "convert a hexadecimal number to base58-check encoding" - from ..proto.btc import _b58chk_encode - return _b58chk_encode( bytes.fromhex(hexstr) ) + from ..proto.common import b58chk_encode + return b58chk_encode( bytes.fromhex(hexstr) ) def b58chktohex(self,b58chk_num:'sstr'): "convert a base58-check encoded number to hexadecimal" - from ..proto.btc import _b58chk_decode - return _b58chk_decode(b58chk_num).hex() + from ..proto.common import b58chk_decode + return b58chk_decode(b58chk_num).hex() def hextob32(self,hexstr:'sstr',pad=0): "convert a hexadecimal number to MMGen's flavor of base 32" diff --git a/mmgen/xmrwallet.py b/mmgen/xmrwallet.py index e140cb94..5ab52700 100755 --- a/mmgen/xmrwallet.py +++ b/mmgen/xmrwallet.py @@ -26,7 +26,8 @@ from .common import * from .objmethods import Hilite,InitErrors from .obj import CoinTxID from .seed import SeedID -from .protocol import init_proto,_b58a +from .protocol import init_proto +from .proto.common import b58a from .addr import CoinAddr,AddrIdx from .addrlist import KeyAddrList,AddrIdxList from .rpc import MoneroRPCClientRaw,MoneroWalletRPCClient,json_encoder @@ -36,7 +37,7 @@ xmrwallet_uarg_info = ( lambda e,hp: { 'daemon': e('HOST:PORT', hp), 'tx_relay_daemon': e('HOST:PORT[:PROXY_HOST:PROXY_PORT]', rf'({hp})(?::({hp}))?'), - 'transfer_spec': e('SOURCE_WALLET_NUM:ACCOUNT:ADDRESS,AMOUNT', rf'(\d+):(\d+):([{_b58a}]+),([0-9.]+)'), + 'transfer_spec': e('SOURCE_WALLET_NUM:ACCOUNT:ADDRESS,AMOUNT', rf'(\d+):(\d+):([{b58a}]+),([0-9.]+)'), 'sweep_spec': e('SOURCE_WALLET_NUM:ACCOUNT[,DEST_WALLET_NUM]', r'(\d+):(\d+)(?:,(\d+))?'), })( namedtuple('uarg_info_entry',['annot','pat']),