new classes: SeedShareMaster, SeedShareMasterJoining
- 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.
This commit is contained in:
parent
b0105598da
commit
237567bca6
2 changed files with 104 additions and 11 deletions
|
|
@ -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'
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue