Browse Source

move baseconv class to its own module, baseconv.py

The MMGen Project 5 years ago
parent
commit
9cc0777f54
10 changed files with 205 additions and 173 deletions
  1. 1 0
      mmgen/addr.py
  2. 196 0
      mmgen/baseconv.py
  3. 2 1
      mmgen/bip39.py
  4. 1 2
      mmgen/protocol.py
  5. 1 0
      mmgen/seed.py
  6. 1 0
      mmgen/tx.py
  7. 0 169
      mmgen/util.py
  8. 1 0
      setup.py
  9. 1 0
      test/tooltest2.py
  10. 1 1
      test/unit_tests_d/ut_baseconv.py

+ 1 - 0
mmgen/addr.py

@@ -23,6 +23,7 @@ addr.py:  Address generation/display routines for the MMGen suite
 from hashlib import sha256,sha512
 from mmgen.common import *
 from mmgen.obj import *
+from mmgen.baseconv import *
 
 pnm = g.proj_name
 

+ 196 - 0
mmgen/baseconv.py

@@ -0,0 +1,196 @@
+#!/usr/bin/env python3
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2019 The MMGen Project <mmgen@tuta.io>
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+"""
+baseconv.py:  base conversion class for the MMGen suite
+"""
+
+from hashlib import sha256
+from mmgen.exception import *
+
+def is_b58_str(s): return set(list(s)) <= set(baseconv.digits['b58'])
+def is_b32_str(s): return set(list(s)) <= set(baseconv.digits['b32'])
+
+class baseconv(object):
+
+	desc = {
+		'b58':   ('base58',               'base58-encoded data'),
+		'b32':   ('MMGen base32',         'MMGen base32-encoded data created using simple base conversion'),
+		'b16':   ('hexadecimal string',   'base16 (hexadecimal) string data'),
+		'b10':   ('base10 string',        'base10 (decimal) string data'),
+		'b8':    ('base8 string',         'base8 (octal) string data'),
+		'b6d':   ('base6d (die roll)',    'base6 data using the digits from one to six'),
+		'mmgen': ('MMGen native mnemonic',
+		'MMGen native mnemonic seed phrase data created using old Electrum wordlist and simple base conversion'),
+	}
+	digits = {
+		'b58': tuple('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'),
+		'b32': tuple('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),
+		'b16': tuple('0123456789abcdef'),
+		'b10': tuple('0123456789'),
+		'b8':  tuple('01234567'),
+		'b6d': tuple('123456'),
+	}
+	mn_base = 1626 # tirosh list is 1633 words long!
+	mn_ids = ('mmgen','tirosh')
+	wl_chksums = {
+		'mmgen':  '5ca31424',
+		'tirosh': '48f05e1f', # tirosh truncated to mn_base (1626)
+		# 'tirosh1633': '1a5faeff'
+	}
+	seed_pad_lens = {
+		'b58': { 16:22, 24:33, 32:44 },
+		'b6d': { 16:50, 24:75, 32:100 },
+	}
+	seed_pad_lens_rev = {
+		'b58': { 22:16, 33:24, 44:32 },
+		'b6d': { 50:16, 75:24, 100:32 },
+	}
+
+	@classmethod
+	def init_mn(cls,mn_id):
+		assert mn_id in cls.mn_ids
+		if mn_id == 'mmgen':
+			from mmgen.mn_electrum import words
+			cls.digits[mn_id] = words
+		elif mn_id == 'tirosh':
+			from mmgen.mn_tirosh import words
+			cls.digits[mn_id] = words[:cls.mn_base]
+		else: # bip39
+			cls.digits[mn_id] = cls.words
+
+	@classmethod
+	def get_wordlist(cls,wl_id):
+		cls.init_mn(wl_id)
+		return cls.digits[wl_id]
+
+	@classmethod
+	def get_wordlist_chksum(cls,wl_id):
+		cls.init_mn(wl_id)
+		return sha256(' '.join(cls.digits[wl_id]).encode()).hexdigest()[:8]
+
+	@classmethod
+	def check_wordlists(cls):
+		for k,v in list(cls.wl_chksums.items()):
+			res = cls.get_wordlist_chksum(k)
+			assert res == v,'{}: checksum mismatch for {} (should be {})'.format(res,k,v)
+
+	@classmethod
+	def check_wordlist(cls,wl_id):
+		cls.init_mn(wl_id)
+
+		wl = cls.digits[wl_id]
+		from mmgen.util import qmsg,compare_chksums
+		qmsg('Wordlist: {}\nLength: {} words'.format(wl_id,len(wl)))
+		new_chksum = cls.get_wordlist_chksum(wl_id)
+
+		a,b = 'generated','saved'
+		compare_chksums(new_chksum,a,cls.wl_chksums[wl_id],b,die_on_fail=True)
+
+		qmsg('List is sorted') if tuple(sorted(wl)) == wl else die(3,'ERROR: List is not sorted!')
+
+	@classmethod
+	def get_pad(cls,pad,seed_pad_func):
+		"""
+		'pad' argument to baseconv conversion methods must be either None, 'seed' or an integer.
+		If None, output of minimum (but never zero) length will be produced.
+		If 'seed', output length will be mapped from input length using data in seed_pad_lens.
+		If an integer, the string, hex string or byte output will be padded to this length.
+		"""
+		if pad == None:
+			return 0
+		elif type(pad) == int:
+			return pad
+		elif pad == 'seed':
+			return seed_pad_func()
+		else:
+			m = "{!r}: illegal value for 'pad' (must be None,'seed' or int)"
+			raise BaseConversionPadError(m.format(pad))
+
+	@classmethod
+	def tohex(cls,words_arg,wl_id,pad=None):
+		"convert string or list data of base 'wl_id' to hex string"
+		return cls.tobytes(words_arg,wl_id,pad//2 if type(pad)==int else pad).hex()
+
+	@classmethod
+	def tobytes(cls,words_arg,wl_id,pad=None):
+		"convert string or list data of base 'wl_id' to byte string"
+
+		words = words_arg if isinstance(words_arg,(list,tuple)) else tuple(words_arg.strip())
+		desc = cls.desc[wl_id][0]
+
+		if len(words) == 0:
+			raise BaseConversionError('empty {} data'.format(desc))
+
+		def get_seed_pad():
+			assert wl_id in cls.seed_pad_lens_rev,'seed padding not supported for base {!r}'.format(wl_id)
+			d = cls.seed_pad_lens_rev[wl_id]
+			if not len(words) in d:
+				m = '{}: invalid length for seed-padded {} data in base conversion'
+				raise BaseConversionError(m.format(len(words),desc))
+			return d[len(words)]
+
+		pad_val = max(cls.get_pad(pad,get_seed_pad),1)
+		wl = cls.digits[wl_id]
+		base = len(wl)
+
+		if not set(words) <= set(wl):
+			m = ('{w!r}:','seed data')[pad=='seed'] + ' not in {d} format'
+			raise BaseConversionError(m.format(w=words_arg,d=desc))
+
+		ret = sum([wl.index(words[::-1][i])*(base**i) for i in range(len(words))])
+		bl = ret.bit_length()
+		return ret.to_bytes(max(pad_val,bl//8+bool(bl%8)),'big')
+
+	@classmethod
+	def fromhex(cls,hexstr,wl_id,pad=None,tostr=False):
+		"convert hex string to list or string data of base 'wl_id'"
+
+		from mmgen.util import is_hex_str
+		if not is_hex_str(hexstr):
+			m = ('{h!r}:','seed data')[pad=='seed'] + ' not a hexadecimal string'
+			raise HexadecimalStringError(m.format(h=hexstr))
+
+		return cls.frombytes(bytes.fromhex(hexstr),wl_id,pad,tostr)
+
+	@classmethod
+	def frombytes(cls,bytestr,wl_id,pad=None,tostr=False):
+		"convert byte string to list or string data of base 'wl_id'"
+
+		if not bytestr:
+			raise BaseConversionError('empty data not allowed in base conversion')
+
+		def get_seed_pad():
+			assert wl_id in cls.seed_pad_lens,'seed padding not supported for base {!r}'.format(wl_id)
+			d = cls.seed_pad_lens[wl_id]
+			if not len(bytestr) in d:
+				m = '{}: invalid byte length for seed data in seed-padded base conversion'
+				raise SeedLengthError(m.format(len(bytestr)))
+			return d[len(bytestr)]
+
+		pad = max(cls.get_pad(pad,get_seed_pad),1)
+		wl = cls.digits[wl_id]
+		base = len(wl)
+
+		num = int.from_bytes(bytestr,'big')
+		ret = []
+		while num:
+			ret.append(num % base)
+			num //= base
+		o = [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
+		return ''.join(o) if tostr else o

+ 2 - 1
mmgen/bip39.py

@@ -23,7 +23,8 @@ bip39.py - Data and routines for BIP39 mnemonic seed phrases
 from hashlib import sha256
 
 from mmgen.exception import *
-from mmgen.util import baseconv,is_hex_str
+from mmgen.baseconv import *
+from mmgen.util import is_hex_str
 
 # implements a subset of the baseconv API
 class bip39(baseconv):

+ 1 - 2
mmgen/protocol.py

@@ -25,6 +25,7 @@ from mmgen.util import msg,ymsg,Msg,ydie
 from mmgen.obj import MMGenObject,BTCAmt,LTCAmt,BCHAmt,B2XAmt,ETHAmt
 from mmgen.globalvars import g
 import mmgen.bech32 as bech32
+from mmgen.baseconv import baseconv,is_b58_str
 
 def hash160(hexnum): # take hex, return hex - OP_HASH160
 	return hashlib.new('ripemd160',hashlib.sha256(bytes.fromhex(hexnum)).digest()).hexdigest()
@@ -407,13 +408,11 @@ class MoneroProtocol(DummyWIF,BitcoinProtocolAddrgen):
 	def verify_addr(cls,addr,hex_width,return_dict=False):
 
 		def b58dec(addr_str):
-			from mmgen.util import baseconv
 			l = len(addr_str)
 			a = ''.join([baseconv.tohex(addr_str[i*11:i*11+11],'b58',pad=16) for i in range(l//11)])
 			b = baseconv.tohex(addr_str[-(l%11):],'b58',pad=10)
 			return a + b
 
-		from mmgen.util import is_b58_str
 		assert is_b58_str(addr),'Not valid base-58 string'
 		assert len(addr) == cls.addr_width,'Incorrect width'
 

+ 1 - 0
mmgen/seed.py

@@ -25,6 +25,7 @@ import os
 from mmgen.common import *
 from mmgen.obj import *
 from mmgen.crypto import *
+from mmgen.baseconv import *
 
 pnm = g.proj_name
 

+ 1 - 0
mmgen/tx.py

@@ -24,6 +24,7 @@ import sys,os,json
 from stat import *
 from mmgen.common import *
 from mmgen.obj import *
+from mmgen.baseconv import *
 
 wmsg = lambda k: {
 	'addr_in_addrfile_only': """

+ 0 - 169
mmgen/util.py

@@ -269,8 +269,6 @@ def is_int(s):
 def is_hex_str(s):    return set(list(s.lower())) <= set(list(hexdigits.lower()))
 def is_hex_str_lc(s): return set(list(s))         <= set(list(hexdigits.lower()))
 def is_hex_str_uc(s): return set(list(s))         <= set(list(hexdigits.upper()))
-def is_b58_str(s):    return set(list(s))         <= set(baseconv.digits['b58'])
-def is_b32_str(s):    return set(list(s))         <= set(baseconv.digits['b32'])
 
 def is_ascii(s,enc='ascii'):
 	try:    s.decode(enc)
@@ -279,173 +277,6 @@ def is_ascii(s,enc='ascii'):
 
 def is_utf8(s): return is_ascii(s,enc='utf8')
 
-class baseconv(object):
-
-	desc = {
-		'b58':   ('base58',               'base58-encoded data'),
-		'b32':   ('MMGen base32',         'MMGen base32-encoded data created using simple base conversion'),
-		'b16':   ('hexadecimal string',   'base16 (hexadecimal) string data'),
-		'b10':   ('base10 string',        'base10 (decimal) string data'),
-		'b8':    ('base8 string',         'base8 (octal) string data'),
-		'b6d':   ('base6d (die roll)',    'base6 data using the digits from one to six'),
-		'mmgen': ('MMGen native mnemonic',
-		'MMGen native mnemonic seed phrase data created using old Electrum wordlist and simple base conversion'),
-	}
-	digits = {
-		'b58': tuple('123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'),
-		'b32': tuple('ABCDEFGHIJKLMNOPQRSTUVWXYZ234567'),
-		'b16': tuple('0123456789abcdef'),
-		'b10': tuple('0123456789'),
-		'b8':  tuple('01234567'),
-		'b6d': tuple('123456'),
-	}
-	mn_base = 1626 # tirosh list is 1633 words long!
-	mn_ids = ('mmgen','tirosh')
-	wl_chksums = {
-		'mmgen':  '5ca31424',
-		'tirosh': '48f05e1f', # tirosh truncated to mn_base (1626)
-		# 'tirosh1633': '1a5faeff'
-	}
-	seed_pad_lens = {
-		'b58': { 16:22, 24:33, 32:44 },
-		'b6d': { 16:50, 24:75, 32:100 },
-	}
-	seed_pad_lens_rev = {
-		'b58': { 22:16, 33:24, 44:32 },
-		'b6d': { 50:16, 75:24, 100:32 },
-	}
-
-	@classmethod
-	def init_mn(cls,mn_id):
-		assert mn_id in cls.mn_ids
-		if mn_id == 'mmgen':
-			from mmgen.mn_electrum import words
-			cls.digits[mn_id] = words
-		elif mn_id == 'tirosh':
-			from mmgen.mn_tirosh import words
-			cls.digits[mn_id] = words[:cls.mn_base]
-		else: # bip39
-			cls.digits[mn_id] = cls.words
-
-	@classmethod
-	def get_wordlist(cls,wl_id):
-		cls.init_mn(wl_id)
-		return cls.digits[wl_id]
-
-	@classmethod
-	def get_wordlist_chksum(cls,wl_id):
-		cls.init_mn(wl_id)
-		return sha256(' '.join(cls.digits[wl_id]).encode()).hexdigest()[:8]
-
-	@classmethod
-	def check_wordlists(cls):
-		for k,v in list(cls.wl_chksums.items()):
-			res = cls.get_wordlist_chksum(k)
-			assert res == v,'{}: checksum mismatch for {} (should be {})'.format(res,k,v)
-
-	@classmethod
-	def check_wordlist(cls,wl_id):
-		cls.init_mn(wl_id)
-
-		wl = cls.digits[wl_id]
-		qmsg('Wordlist: {}\nLength: {} words'.format(wl_id,len(wl)))
-		new_chksum = cls.get_wordlist_chksum(wl_id)
-
-		a,b = 'generated','saved'
-		compare_chksums(new_chksum,a,cls.wl_chksums[wl_id],b,die_on_fail=True)
-
-		qmsg('List is sorted') if tuple(sorted(wl)) == wl else die(3,'ERROR: List is not sorted!')
-
-	@classmethod
-	def get_pad(cls,pad,seed_pad_func):
-		"""
-		'pad' argument to baseconv conversion methods must be either None, 'seed' or an integer.
-		If None, output of minimum (but never zero) length will be produced.
-		If 'seed', output length will be mapped from input length using data in seed_pad_lens.
-		If an integer, the string, hex string or byte output will be padded to this length.
-		"""
-		if pad == None:
-			return 0
-		elif type(pad) == int:
-			return pad
-		elif pad == 'seed':
-			return seed_pad_func()
-		else:
-			m = "{!r}: illegal value for 'pad' (must be None,'seed' or int)"
-			raise BaseConversionPadError(m.format(pad))
-
-	@classmethod
-	def tohex(cls,words_arg,wl_id,pad=None):
-		"convert string or list data of base 'wl_id' to hex string"
-		return cls.tobytes(words_arg,wl_id,pad//2 if type(pad)==int else pad).hex()
-
-	@classmethod
-	def tobytes(cls,words_arg,wl_id,pad=None):
-		"convert string or list data of base 'wl_id' to byte string"
-
-		words = words_arg if isinstance(words_arg,(list,tuple)) else tuple(words_arg.strip())
-		desc = cls.desc[wl_id][0]
-
-		if len(words) == 0:
-			raise BaseConversionError('empty {} data'.format(desc))
-
-		def get_seed_pad():
-			assert wl_id in cls.seed_pad_lens_rev,'seed padding not supported for base {!r}'.format(wl_id)
-			d = cls.seed_pad_lens_rev[wl_id]
-			if not len(words) in d:
-				m = '{}: invalid length for seed-padded {} data in base conversion'
-				raise BaseConversionError(m.format(len(words),desc))
-			return d[len(words)]
-
-		pad_val = max(cls.get_pad(pad,get_seed_pad),1)
-		wl = cls.digits[wl_id]
-		base = len(wl)
-
-		if not set(words) <= set(wl):
-			m = ('{w!r}:','seed data')[pad=='seed'] + ' not in {d} format'
-			raise BaseConversionError(m.format(w=words_arg,d=desc))
-
-		ret = sum([wl.index(words[::-1][i])*(base**i) for i in range(len(words))])
-		bl = ret.bit_length()
-		return ret.to_bytes(max(pad_val,bl//8+bool(bl%8)),'big')
-
-	@classmethod
-	def fromhex(cls,hexstr,wl_id,pad=None,tostr=False):
-		"convert hex string to list or string data of base 'wl_id'"
-
-		if not is_hex_str(hexstr):
-			m = ('{h!r}:','seed data')[pad=='seed'] + ' not a hexadecimal string'
-			raise HexadecimalStringError(m.format(h=hexstr))
-
-		return cls.frombytes(bytes.fromhex(hexstr),wl_id,pad,tostr)
-
-	@classmethod
-	def frombytes(cls,bytestr,wl_id,pad=None,tostr=False):
-		"convert byte string to list or string data of base 'wl_id'"
-
-		if not bytestr:
-			raise BaseConversionError('empty data not allowed in base conversion')
-
-		def get_seed_pad():
-			assert wl_id in cls.seed_pad_lens,'seed padding not supported for base {!r}'.format(wl_id)
-			d = cls.seed_pad_lens[wl_id]
-			if not len(bytestr) in d:
-				m = '{}: invalid byte length for seed data in seed-padded base conversion'
-				raise SeedLengthError(m.format(len(bytestr)))
-			return d[len(bytestr)]
-
-		pad = max(cls.get_pad(pad,get_seed_pad),1)
-		wl = cls.digits[wl_id]
-		base = len(wl)
-
-		num = int.from_bytes(bytestr,'big')
-		ret = []
-		while num:
-			ret.append(num % base)
-			num //= base
-		o = [wl[n] for n in [0] * (pad-len(ret)) + ret[::-1]]
-		return ''.join(o) if tostr else o
-
 def match_ext(addr,ext):
 	return addr.split('.')[-1] == ext
 

+ 1 - 0
setup.py

@@ -83,6 +83,7 @@ setup(
 			'mmgen.__init__',
 			'mmgen.addr',
 			'mmgen.altcoin',
+			'mmgen.baseconv',
 			'mmgen.bech32',
 			'mmgen.bip39',
 			'mmgen.color',

+ 1 - 0
test/tooltest2.py

@@ -32,6 +32,7 @@ from mmgen.common import *
 from test.common import *
 from mmgen.obj import is_wif,is_coin_addr
 from mmgen.seed import is_bip39_mnemonic,is_mmgen_mnemonic
+from mmgen.baseconv import *
 
 NL = ('\n','\r\n')[g.platform=='win']
 

+ 1 - 1
test/unit_tests_d/ut_baseconv.py

@@ -133,7 +133,7 @@ class unit_test(object):
 
 		msg_r('Testing base conversion routines...')
 
-		from mmgen.util import baseconv
+		from mmgen.baseconv import baseconv
 		perr = "length of {!r} less than pad length ({})"
 		rerr = "return value ({!r}) does not match reference value ({!r})"