From 237567bca628861768e15973d2d40f67d026e968 Mon Sep 17 00:00:00 2001 From: MMGen Date: Tue, 11 Jun 2019 15:55:35 +0000 Subject: [PATCH] new classes: SeedShareMaster, SeedShareMasterJoining MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - create multiple seed splits with a single “master share”. The master share is a deterministic pseudorandom value which is HMAC’ed with the split’s ID string and split count to produce the first share of the split. To ensure uniqueness between splits, the remaining pseudorandom shares have the master share’s index appended to their HMAC key. Each seed has 1024 numerically indexed master shares. --- mmgen/seed.py | 77 ++++++++++++++++++++++++++++--- test/unit_tests_d/ut_seedsplit.py | 38 +++++++++++++-- 2 files changed, 104 insertions(+), 11 deletions(-) diff --git a/mmgen/seed.py b/mmgen/seed.py index 9b118893..e9c63fa2 100755 --- a/mmgen/seed.py +++ b/mmgen/seed.py @@ -206,11 +206,11 @@ class Seed(SeedBase): def subseed_by_seed_id(self,sid,last_idx=None,print_msg=False): return self.subseeds.get_subseed_by_seed_id(sid,last_idx=last_idx,print_msg=print_msg) - def split(self,count,id_str=None): - return SeedShareList(self,count,id_str) + def split(self,count,id_str=None,master_idx=None): + return SeedShareList(self,count,id_str,master_idx) @staticmethod - def join_shares(seed_list): + def join_shares(seed_list,use_master=False,master_idx=1,id_str=None): if not hasattr(seed_list,'__next__'): # seed_list can be iterator or iterable seed_list = iter(seed_list) @@ -227,9 +227,15 @@ class Seed(SeedBase): d.ret ^= int(ss.data.hex(),16) d.count += 1 + if use_master: + master_share = next(seed_list) + for ss in seed_list: add_share(ss) + if use_master: + add_share(SeedShareMasterJoining(master_idx,master_share,id_str,d.count+1).derived_seed) + SeedShareCount(d.count) return Seed(seed_bin=d.ret.to_bytes(d.slen // 8,'big')) @@ -258,20 +264,26 @@ class SubSeed(SeedBase): return scramble_seed(seed.data,scramble_key)[:byte_len] class SeedShareList(SubSeedList): + master_share = None have_short = False split_type = 'N-of-N' count = MMGenImmutableAttr('count',SeedShareCount) id_str = MMGenImmutableAttr('id_str',SeedShareIDString) - def __init__(self,parent_seed,count,id_str=None): + def __init__(self,parent_seed,count,id_str=None,master_idx=None): self.member_type = SeedShare self.parent_seed = parent_seed self.id_str = id_str or 'default' self.count = count + if master_idx: + self.master_share = SeedShareMaster(self,master_idx) + while True: self.data = { 'long': IndexedDict(), 'short': IndexedDict() } + if master_idx: + self.data['long'][self.master_share.derived_seed.sid] = (1,master_idx) self._generate(count-1) self.last_share = SeedShareLast(self) sid = self.last_share.sid @@ -292,6 +304,8 @@ class SeedShareList(SubSeedList): def get_share_by_idx(self,idx): if idx == self.count: return self.last_share + elif self.master_share and idx == 1: + return self.master_share.derived_seed else: ss_idx = SubSeedIdx(str(idx) + 'L') return self.get_subseed_by_ss_idx(ss_idx) @@ -299,6 +313,8 @@ class SeedShareList(SubSeedList): def get_share_by_seed_id(self,sid,last_idx=None): if sid == self.data['long'].key(self.count-1): return self.last_share + elif self.master_share and sid == self.data['long'].key(0): + return self.master_share.derived_seed else: return self.get_subseed_by_seed_id(sid,last_idx=last_idx) @@ -309,17 +325,22 @@ class SeedShareList(SubSeedList): assert self.split_type == 'N-of-N' fs1 = ' {}\n' fs2 = '{i:>5}: {}\n' + mfs1,mfs2,midx,msid = ('','','','') + if self.master_share: + mfs1,mfs2 = (' with master share #{} ({})',' master #{} ({})') + midx,msid = (self.master_share.idx,self.master_share.sid) hdr = ' {} {} ({} bits)\n'.format('Seed:',self.parent_seed.sid.hl(),self.parent_seed.length) - hdr += ' {} {c}-of-{c} (XOR)\n'.format('Split Type:',c=self.count) + hdr += ' {} {c}-of-{c} (XOR){m}\n'.format('Split Type:',c=self.count,m=mfs1.format(midx,msid)) hdr += ' {} {}\n\n'.format('ID String:',self.id_str.hl()) hdr += fs1.format('Shares') hdr += fs1.format('------') sl = self.data['long'].keys - body = (fs2.format(sl[n],i=n+1) for n in range(len(self))) + body1 = fs2.format(sl[0]+mfs2.format(midx,msid),i=1) + body = (fs2.format(sl[n],i=n+1) for n in range(1,len(self))) - return hdr + ''.join(body) + return hdr + body1 + ''.join(body) class SeedShare(SubSeed): @@ -333,6 +354,8 @@ class SeedShare(SubSeed): parent_list.count.to_bytes(2,'big',signed=False) + \ idx.to_bytes(2,'big',signed=False) + \ nonce.to_bytes(2,'big',signed=False) + if parent_list.master_share: + scramble_key += b':master:' + parent_list.master_share.idx.to_bytes(2,'big',signed=False) byte_len = seed.length // 8 return scramble_seed(seed.data,scramble_key)[:byte_len] @@ -356,6 +379,46 @@ class SeedShareLast(SubSeed): return ret.to_bytes(seed.length // 8,'big') +class SeedShareMaster(SubSeed): + + idx = MMGenImmutableAttr('idx',MasterShareIdx) + nonce = 0 + + def __init__(self,parent_list,idx): + self.idx = idx + self.parent_seed = parent_list.parent_seed + SeedBase.__init__(self,self.make_base_seed_bin()) + + self.derived_seed = SeedBase(self.make_derived_seed_bin(parent_list.id_str,parent_list.count)) + + @property + def fn_stem(self): + return '{}-master_share{}[{}]'.format(self.parent_seed.sid,self.idx,self.sid) + + def make_base_seed_bin(self): + # field maximums: idx: 65535 (1024) + scramble_key = b'master:' + self.idx.to_bytes(2,'big',signed=False) + byte_len = self.parent_seed.length // 8 + return scramble_seed(self.parent_seed.data,scramble_key)[:byte_len] + + def make_derived_seed_bin(self,id_str,count): + # field maximums: id_str: none (256 chars), count: 65535 (1024) + scramble_key = id_str.encode() + b':' + count.to_bytes(2,'big',signed=False) + byte_len = self.length // 8 + return scramble_seed(self.data,scramble_key)[:byte_len] + +class SeedShareMasterJoining(SeedShareMaster): + + id_str = MMGenImmutableAttr('id_str',SeedShareIDString) + count = MMGenImmutableAttr('count',SeedShareCount) + + def __init__(self,idx,base_seed,id_str,count): + SeedBase.__init__(self,seed_bin=base_seed.data) + + self.id_str = id_str or 'default' + self.count = count + self.derived_seed = SeedBase(self.make_derived_seed_bin(self.id_str,self.count)) + class SeedSource(MMGenObject): desc = g.proj_name + ' seed source' diff --git a/test/unit_tests_d/ut_seedsplit.py b/test/unit_tests_d/ut_seedsplit.py index 7a2a767d..6b8131d0 100755 --- a/test/unit_tests_d/ut_seedsplit.py +++ b/test/unit_tests_d/ut_seedsplit.py @@ -11,7 +11,7 @@ class unit_test(object): from mmgen.seed import Seed from mmgen.obj import SeedShareIdx - def basic_ops(): + def basic_ops(master_idx): test_data = { 'default': ( (8,'4710FBF0','B3D9411B','2670E83D','D1FC57ED','AE49CABE','63FFBA62',256), @@ -24,9 +24,32 @@ class unit_test(object): (4,'43670520','77140076','EA82CB30','80F7AEDE','D168D768','77BE57AA',128), ) } + test_data_master = { + '1': { + 'default': ( + (8,'4710FBF0','B512A312','3588E156','9374255D','3E87A907','752A2E4E',256), + (4,'43670520','05880E2B','C6B438D4','5FF9B5DF','778E9C60','2C01F046',128) ), + 'φυβαρ': ( + (8,'4710FBF0','5FA963B0','69A1F56A','25789CC4','9777A750','E17B9B8B',256), + (4,'43670520','AF8BFDF8','66F319BE','A5E40978','927549D2','93B2418B',128), + ) + }, + '5': { + 'default': ( + (8,'4710FBF0','A8A34BC0','F69B6CF8','234B5DCD','BB004DC5','08DC9776',256), + (4,'43670520','C887A2D6','86AE9445','3188AD3D','07339882','BE3FE72A',128) ), + + 'φυβαρ': ( + (8,'4710FBF0','89C35D99','B1CD5854','8414652C','32C24668','17CA1E19',256), + (4,'43670520','06929789','32E8E375','C6AC3C9D','4BEA2AB2','15AFC7F2',128) + ) + } + } + if master_idx: + test_data = test_data_master[str(master_idx)] for id_str in (None,'default','φυβαρ'): - msg_r('Testing basic ops (id_str={!r})...'.format(id_str)) + msg_r('Testing basic ops (id_str={!r}, master_idx={})...'.format(id_str,master_idx)) vmsg('') for a,b,c,d,e,f,h,i in test_data[id_str if id_str is not None else 'default']: @@ -36,7 +59,7 @@ class unit_test(object): for share_count,j,k,l in ((2,c,c,d),(5,e,f,h)): - shares = seed.split(share_count,id_str) + shares = seed.split(share_count,id_str,master_idx) A = len(shares) assert A == share_count, A @@ -59,6 +82,11 @@ class unit_test(object): A = shares.join().sid assert A == b, A + if master_idx: + slist = [shares.get_share_by_idx(i+1) for i in range(1,len(shares))] + A = Seed.join_shares([shares.master_share]+slist,True,master_idx,id_str).sid + assert A == b, A + msg('OK') def defaults_and_limits(): @@ -109,7 +137,9 @@ class unit_test(object): vmsg_r('\n{} collisions, last_sid {}'.format(collisions,last_sid)) msg('OK') - basic_ops() + basic_ops(master_idx=None) + basic_ops(master_idx=1) + basic_ops(master_idx=5) defaults_and_limits() collisions()