new class: SubSeedList

This commit is contained in:
The MMGen Project 2019-06-02 16:44:14 +00:00
commit 2952797621
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 105 additions and 77 deletions

View file

@ -64,71 +64,82 @@ class SeedBase(MMGenObject):
self.sid = SeedID(seed=self)
self.length = len(seed_bin) * 8
class Seed(SeedBase):
class SubSeedList(MMGenObject):
def __init__(self,seed_bin=None):
def __init__(self,parent_seed,have_short=False):
self.parent_seed = parent_seed
self.have_short = have_short
from collections import OrderedDict
self.subseeds = { 'long': OrderedDict(), 'short': OrderedDict() }
SeedBase.__init__(self,seed_bin=seed_bin)
self.data = { 'long': OrderedDict(), 'short': OrderedDict() }
def subseed(self,ss_idx_in,print_msg=False):
def __len__(self):
return len(self.data['long'])
def get_params_by_ss_idx(self,ss_idx):
sid = list(self.data[ss_idx.type].keys())[ss_idx.idx-1]
idx,nonce = self.data[ss_idx.type][sid]
return (sid,idx,nonce)
def get_subseed_by_ss_idx(self,ss_idx_in,print_msg=False):
ss_idx = SubSeedIdx(ss_idx_in)
if print_msg:
msg_r('{} {} of {}...'.format(
green('Generating subseed'),
ss_idx.hl(),
self.sid.hl(),
self.parent_seed.sid.hl(),
))
if ss_idx.idx > len(self.subseeds['long']):
self.gen_subseeds(ss_idx.idx)
sid = list(self.subseeds[ss_idx.type].keys())[ss_idx.idx-1]
idx,nonce = self.subseeds[ss_idx.type][sid]
if ss_idx.idx > len(self):
self.generate(ss_idx.idx)
sid,idx,nonce = self.get_params_by_ss_idx(ss_idx)
if print_msg:
msg('\b\b\b => {}'.format(SeedID.hlc(sid)))
assert idx == ss_idx.idx, "{} != {}: subseed list idx does not match subseed idx!".format(idx,ss_idx.idx)
return SubSeed(self,idx,nonce,length=ss_idx.type)
return SubSeed(self.parent_seed,idx,nonce,length=ss_idx.type)
def existing_subseed_by_seed_id(self,sid):
for k in ('long','short'):
if sid in self.subseeds[k]:
idx,nonce = self.subseeds[k][sid]
return SubSeed(self,idx,nonce,length=k)
def get_existing_subseed_by_seed_id(self,sid):
for k in ('long','short') if self.have_short else ('long',):
if sid in self.data[k]:
idx,nonce = self.data[k][sid]
return SubSeed(self.parent_seed,idx,nonce,length=k)
def subseed_by_seed_id(self,sid,last_idx=None,print_msg=False):
def get_subseed_by_seed_id(self,sid,last_idx=None,print_msg=False):
def do_msg(seed):
def do_msg(subseed):
if print_msg:
qmsg('{} {} ({}:{})'.format(
green('Found subseed'),
seed.sid.hl(),
self.sid.hl(),
seed.ss_idx.hl(),
subseed.sid.hl(),
self.parent_seed.sid.hl(),
subseed.ss_idx.hl(),
))
if last_idx == None:
last_idx = g.subseeds
seed = self.existing_subseed_by_seed_id(sid)
if seed:
do_msg(seed)
return seed
subseed = self.get_existing_subseed_by_seed_id(sid)
if subseed:
do_msg(subseed)
return subseed
if len(self.subseeds['long']) >= last_idx:
if len(self) >= last_idx:
return None
self.gen_subseeds(last_idx,last_sid=sid)
self.generate(last_idx,last_sid=sid)
seed = self.existing_subseed_by_seed_id(sid)
if seed:
do_msg(seed)
return seed
subseed = self.get_existing_subseed_by_seed_id(sid)
if subseed:
do_msg(subseed)
return subseed
def gen_subseeds(self,last_idx=None,last_sid=None):
def generate(self,last_idx=None,last_sid=None):
if last_idx == None:
last_idx = g.subseeds
first_idx = len(self.subseeds['long']) + 1
first_idx = len(self) + 1
if first_idx > last_idx:
return None
@ -138,45 +149,59 @@ class Seed(SeedBase):
def add_subseed(idx,length):
for nonce in range(SubSeed.max_nonce): # use nonce to handle Seed ID collisions
sid = make_chksum_8(SubSeedBase.make_subseed_bin(self,idx,nonce,length))
if not (sid in self.subseeds['long'] or sid in self.subseeds['short'] or sid == self.sid):
self.subseeds[length][sid] = (idx,nonce)
sid = make_chksum_8(SubSeedBase.make_subseed_bin(self.parent_seed,idx,nonce,length))
if not (sid in self.data['long'] or sid in self.data['short'] or sid == self.parent_seed.sid):
self.data[length][sid] = (idx,nonce)
return last_sid == sid
elif g.debug_subseed: # should get ≈450 collisions for first 1,000,000 subseeds
k = ('long','short')[sid in self.subseeds['short']]
k = ('long','short')[sid in self.data['short']]
m1 = 'add_subseed(idx={},{}):'.format(idx,length)
if sid == self.sid:
if sid == self.parent_seed.sid:
m2 = 'collision with parent Seed ID {},'.format(sid)
else:
m2 = 'collision with ID {} (idx={},{}),'.format(sid,self.subseeds[k][sid][0],k)
m2 = 'collision with ID {} (idx={},{}),'.format(sid,self.data[k][sid][0],k)
msg('{:30} {:46} incrementing nonce to {}'.format(m1,m2,nonce+1))
else: # must exit here, as this could leave self.subseeds in inconsistent state
else: # must exit here, as this could leave self.data in inconsistent state
raise SubSeedNonceRangeExceeded('add_subseed(): nonce range exceeded')
for idx in SubSeedIdxRange(first_idx,last_idx).iterate():
if add_subseed(idx,'long') + add_subseed(idx,'short'):
break
match1 = add_subseed(idx,'long')
match2 = add_subseed(idx,'short') if self.have_short else False
if match1 or match2: break
def fmt_subseeds(self,first_idx,last_idx):
def format(self,first_idx,last_idx):
r = SubSeedIdxRange(first_idx,last_idx)
if len(self.subseeds['long']) < last_idx:
self.gen_subseeds(last_idx)
if len(self) < last_idx:
self.generate(last_idx)
fs1 = '{:>18} {:>18}\n'
fs2 = '{i:>7}L: {:8} {i:>7}S: {:8}\n'
hdr = '{:>16} {} ({} bits)\n\n'.format('Parent Seed:',self.sid.hl(),self.length)
hdr = '{:>16} {} ({} bits)\n\n'.format('Parent Seed:',self.parent_seed.sid.hl(),self.parent_seed.length)
hdr += fs1.format('Long Subseeds','Short Subseeds')
hdr += fs1.format('-------------','--------------')
sl = tuple(self.subseeds['long'])
ss = tuple(self.subseeds['short'])
sl = tuple(self.data['long'])
ss = tuple(self.data['short'])
body = (fs2.format(sl[n-1],ss[n-1],i=n) for n in r.iterate())
return hdr + ''.join(body)
class Seed(SeedBase):
def __init__(self,seed_bin=None):
self.subseeds = SubSeedList(self,have_short=True)
SeedBase.__init__(self,seed_bin=seed_bin)
def subseed(self,ss_idx_in,print_msg=False):
return self.subseeds.get_subseed_by_ss_idx(ss_idx_in,print_msg=print_msg)
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)
class SubSeedBase(MMGenObject):
max_nonce = 1000

View file

@ -697,7 +697,7 @@ class MMGenToolCmdWallet(MMGenToolCmdBase):
opt.quiet = True
sf = get_seed_file([wallet] if wallet else [],1)
from mmgen.seed import SeedSource
return SeedSource(sf).seed.fmt_subseeds(*SubSeedIdxRange(subseed_idx_range))
return SeedSource(sf).seed.subseeds.format(*SubSeedIdxRange(subseed_idx_range))
def gen_key(self,mmgen_addr:str,wallet=''):
"generate a single MMGen WIF key from default or specified wallet"

View file

@ -36,27 +36,26 @@ class subseed(object):
assert subseed.ss_idx == h, subseed.ss_idx
seed2 = Seed(seed_bin)
s2s = seed2.subseeds['short']
s2l = seed2.subseeds['long']
ss2_list = seed2.subseeds
seed2.gen_subseeds(1)
assert len(s2s) == 1, len(s2s)
seed2.subseeds.generate(1)
assert len(ss2_list) == 1, len(ss2_list)
seed2.gen_subseeds(1) # do nothing
seed2.gen_subseeds(2) # append one item
seed2.subseeds.generate(1) # do nothing
seed2.subseeds.generate(2) # append one item
seed2.gen_subseeds(5)
assert len(s2s) == 5, len(s2s)
seed2.subseeds.generate(5)
assert len(ss2_list) == 5, len(ss2_list)
seed2.gen_subseeds(3) # do nothing
assert len(s2l) == 5, len(s2l)
seed2.subseeds.generate(3) # do nothing
assert len(ss2_list) == 5, len(ss2_list)
seed2.gen_subseeds(10)
assert len(s2s) == 10, len(s2s)
seed2.subseeds.generate(10)
assert len(ss2_list) == 10, len(ss2_list)
assert seed.pformat() == seed2.pformat()
# assert seed.pformat() == seed2.pformat() # TODO: deal with recursion in pformat()
s = seed.fmt_subseeds(1,g.subseeds)
s = seed.subseeds.format(1,g.subseeds)
s_lines = s.strip().split('\n')
assert len(s_lines) == g.subseeds + 4, s
@ -71,7 +70,7 @@ class subseed(object):
b = [e for e in s_lines if ' 5S:' in e][0].strip().split()[3]
assert a == b, b
s = seed.fmt_subseeds(g.subseeds+1,g.subseeds+2)
s = seed.subseeds.format(g.subseeds+1,g.subseeds+2)
s_lines = s.strip().split('\n')
assert len(s_lines) == 6, s
@ -80,9 +79,11 @@ class subseed(object):
b = [e for e in s_lines if ' {}:'.format(ss_idx) in e][0].strip().split()[3]
assert a == b, b
s = seed.fmt_subseeds(1,2)
s = seed.subseeds.format(1,10)
s_lines = s.strip().split('\n')
assert len(s_lines) == 6, s
assert len(s_lines) == 14, s
vmsg_r('\n{}'.format(s))
msg('OK')
@ -91,24 +92,26 @@ class subseed(object):
seed_bin = bytes.fromhex('deadbeef' * 8)
seed = Seed(seed_bin)
seed.gen_subseeds()
seed.subseeds.generate()
ss = seed.subseeds
assert len(ss['short']) == g.subseeds, ss['short']
assert len(ss['long']) == g.subseeds, ss['long']
assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
assert len(ss) == g.subseeds, len(ss)
seed = Seed(seed_bin)
seed.subseed_by_seed_id('EEEEEEEE')
ss = seed.subseeds
assert len(ss['short']) == g.subseeds, ss['short']
assert len(ss['long']) == g.subseeds, ss['long']
assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
assert len(ss) == g.subseeds, len(ss)
seed = Seed(seed_bin)
subseed = seed.subseed_by_seed_id('803B165C')
assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
assert subseed.sid == '803B165C', subseed.sid
assert subseed.idx == 3, subseed.idx
seed = Seed(seed_bin)
subseed = seed.subseed_by_seed_id('803B165C',last_idx=1)
assert len(ss.data['long']) == len(ss.data['short']), len(ss.data['short'])
assert subseed == None, subseed
r = SubSeedIdxRange('1-5')
@ -155,22 +158,22 @@ class subseed(object):
seed_bin = bytes.fromhex('12abcdef' * 8)
seed = Seed(seed_bin)
seed.gen_subseeds(ss_count)
seed.subseeds.generate(ss_count)
ss = seed.subseeds
assert seed.subseed(last_idx).sid == last_sid, seed.subseed(last_idx).sid
for sid in ss['long']:
for sid in ss.data['long']:
# msg(sid)
assert sid not in ss['short']
assert sid not in ss.data['short']
collisions = 0
for k in ('short','long'):
for sid in ss[k]:
collisions += ss[k][sid][1]
for sid in ss.data[k]:
collisions += ss.data[k][sid][1]
assert collisions == collisions_chk, collisions
msg_r('({} collisions) '.format(collisions))
msg_r('({} collisions, last_sid {}) '.format(collisions,last_sid))
msg('OK')
basic_ops()