Browse Source

move coin privkey bytes derivation to derive.py

The MMGen Project 2 years ago
parent
commit
2e1f3b93e4
4 changed files with 73 additions and 24 deletions
  1. 11 21
      mmgen/addrlist.py
  2. 48 0
      mmgen/derive.py
  3. 1 0
      test/unit_tests.py
  4. 13 3
      test/unit_tests_d/ut_addrlist.py

+ 11 - 21
mmgen/addrlist.py

@@ -210,7 +210,6 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 			(chk,rec)[record] )
 
 	def generate(self,seed,addr_idxs):
-		assert type(addr_idxs) is AddrIdxList
 
 		seed = self.scramble_seed(seed.data)
 		dmsg_sc('seed',seed[:8].hex())
@@ -227,31 +226,24 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 			if self.add_p2pkh:
 				ag2 = AddrGenerator( self.proto, 'compressed' )
 
-		t_addrs,out = ( len(addr_idxs), AddrListData() )
-		le = self.entry_type
-		num,pos = (0,0)
-
-		from hashlib import sha256,sha512
 		from .globalvars import g
+		from .derive import derive_coin_privkey_bytes
 
-		while pos != t_addrs:
-			seed = sha512(seed).digest()
-			num += 1 # round
-
-			if num != addr_idxs[pos]:
-				continue
+		t_addrs = len(addr_idxs)
+		le = self.entry_type
+		out = AddrListData()
+		CR = '\n' if g.debug_addrlist else '\r'
 
-			pos += 1
+		for pk_bytes in derive_coin_privkey_bytes(seed,addr_idxs):
 
 			if not g.debug:
-				qmsg_r(f'\rGenerating {self.gen_desc} #{num} ({pos} of {t_addrs})')
+				qmsg_r(f'{CR}Generating {self.gen_desc} #{pk_bytes.idx} ({pk_bytes.pos} of {t_addrs})')
 
-			e = le(proto=self.proto,idx=num)
+			e = le( proto=self.proto, idx=pk_bytes.idx )
 
-			# Secret key is double sha256 of seed hash round /num/
 			e.sec = PrivKey(
 				self.proto,
-				sha256(sha256(seed).digest()).digest(),
+				pk_bytes.data,
 				compressed  = mmtype.compressed,
 				pubkey_type = mmtype.pubkey_type )
 
@@ -269,10 +261,8 @@ class AddrList(MMGenObject): # Address info for a single seed ID
 
 			out.append(e)
 
-			if g.debug_addrlist:
-				Msg(f'generate():\n{e.pfmt()}')
-
-		qmsg('\r{}: {} {}{} generated{}'.format(
+		qmsg('{}{}: {} {}{} generated{}'.format(
+			CR,
 			self.al_id.hl(),
 			t_addrs,
 			self.gen_desc,

+ 48 - 0
mmgen/derive.py

@@ -0,0 +1,48 @@
+#!/usr/bin/env python3
+#
+# mmgen = Multi-Mode GENerator, command-line Bitcoin cold storage solution
+# Copyright (C)2013-2022 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/>.
+
+"""
+derive.py: coin private key secret derivation for the MMGen suite
+"""
+
+from collections import namedtuple
+from hashlib import sha512,sha256
+from .addrlist import AddrIdxList
+
+pk_bytes = namedtuple('coin_privkey_bytes',['idx','pos','data'])
+
+def derive_coin_privkey_bytes(seed,idxs):
+
+	assert isinstance(idxs,AddrIdxList)
+
+	t_keys = len(idxs)
+	pos = 0
+
+	for idx in range( 1, AddrIdxList.max_len+1 ): # key/addr indexes begin from one
+
+		seed = sha512(seed).digest()
+
+		if idx == idxs[pos]:
+
+			pos += 1
+
+			# secret is double sha256 of seed hash round /idx/
+			yield pk_bytes( idx, pos, sha256(sha256(seed).digest()).digest() )
+
+			if pos == t_keys:
+				break

+ 1 - 0
test/unit_tests.py

@@ -52,6 +52,7 @@ If no test is specified, all available tests are run
 sys.argv.insert(1,'--skip-cfg-file')
 
 opts.UserOpts._reset_ok += ('use_internal_keccak_module',)
+g._reset_ok += ('debug_addrlist',)
 
 cmd_args = opts.init(opts_data)
 

+ 13 - 3
test/unit_tests_d/ut_addrlist.py

@@ -10,12 +10,16 @@ from mmgen.addrlist import AddrIdxList,AddrList,KeyList,KeyAddrList
 from mmgen.passwdlist import PasswordList
 from mmgen.protocol import init_proto
 
-def do_test(list_type,chksum,pw_id_str=None,add_kwargs=None):
+def do_test(list_type,chksum,idx_spec=None,pw_id_str=None,add_kwargs=None):
 	qmsg(blue(f'Testing {list_type.__name__}'))
 	proto = init_proto('btc')
 	seed = Seed(seed_bin=bytes.fromhex('feedbead'*8))
 	mmtype = MMGenAddrType(proto,'C')
-	idxs = AddrIdxList('1-3')
+	idxs = AddrIdxList(idx_spec or '1-3')
+
+	if opt.verbose:
+		debug_addrlist_save = g.debug_addrlist
+		g.debug_addrlist = True
 
 	kwargs = {
 		'seed': seed,
@@ -43,12 +47,18 @@ def do_test(list_type,chksum,pw_id_str=None,add_kwargs=None):
 	if chksum:
 		assert al.chksum == chksum, f'{al.chksum} != {chksum}'
 
+	if opt.verbose:
+		g.debug_addrlist = debug_addrlist_save
+
 	return True
 
 class unit_tests:
 
 	def addr(self,name,ut):
-		return do_test(AddrList,'BCE8 082C 0973 A525')
+		return (
+			do_test(AddrList,'BCE8 082C 0973 A525','1-3') and
+			do_test(AddrList,'88FA B04B A380 C1CB','199999,99-101,77-78,7,3,2-9')
+		)
 
 	def key(self,name,ut):
 		return do_test(KeyList,None)