text: # -*- encoding: utf-8 -*-
  """
  KERI
  keri.app.keeping module
  
  Terminology:
      salt is 128 bit 16 char random bytes used as root entropy to derive seed or secret
      private key same as seed or secret for key pair
      seed or secret or private key is crypto suite length dependent random bytes
      public key
  
  txn.put(
              did.encode(),
              json.dumps(certifiable_data).encode("utf-8")
          )
  raw_data = txn.get(did.encode())
      if raw_data is None:
          return None
      return json.loads(raw_data)
  
  ked = json.loads(raw[:size].decode("utf-8"))
  raw = json.dumps(ked, separators=(",", ":"), ensure_ascii=False).encode("utf-8")
  
  """
  import os
  import stat
  import json
  import math
  
  from typing import Union
  from dataclasses import dataclass, asdict, field
  from collections import namedtuple, deque
  
  from hio.base import doing
  
  from .. import kering
  from ..help import helping
  from ..core import coring
  from ..db import dbing, subing, koming
  
  
  Algoage = namedtuple("Algoage", 'randy salty')
  Algos = Algoage(randy='randy', salty='salty')  # randy is rerandomize, salty is use salt
  
  
  @dataclass()
  class PubLot:
      """
      Public key list with indexes and datetime created
      Attributes:
          pubs (list): list of fully qualified Base64 public keys. Defaults to empty.
          ridx (int): rotation index of  set of public keys at establishment event.
                      Includes of key set at inception event is 0.
          kidx (int): key index of starting key in key set in sequence wrt to all
                      public keys. Example if each set has 3 keys then ridx 2 has
                      kidx of 2*3 = 6.
          st (Union[str, int, list]): signing theshold for key set at ridx. May be
                      int, str, or list based on threshold expression type.
          dt (str): datetime in ISO8601 format of when key set was first created
  
  
      """
      pubs: list = field(default_factory=list)  # list qb64 public keys.
      ridx: int = 0  # index of rotation (est event) that uses public key set
      kidx: int = 0  # index of key in sequence of public keys
      st: Union[str, int, list] = '0' # signing threshold
      dt:   str = ""  # datetime ISO8601 when key set created
  
      def __iter__(self):
          return iter(asdict(self))
  
  
  @dataclass()
  class PreSit:
      """
      Prefix's public key situation (sets of public kets)
      """
      old: PubLot = field(default_factory=PubLot)  # previous publot
      new: PubLot = field(default_factory=PubLot)  # newly current publot
      nxt: PubLot = field(default_factory=PubLot)  # next public publot
  
      def __iter__(self):
          return iter(asdict(self))
  
  
  @dataclass()
  class PrePrm:
      """
      Prefix's parameters for creating new key pairs
      """
      pidx: int = 0  # prefix index for this keypair sequence
      algo: str = Algos.salty  # salty default uses indices and salt to create new key pairs
      salt: str = ''  # empty salt  used for salty algo.
      stem: str = ''  # default unique path stem for salty algo
      tier: str = ''  # security tier for stretch index salty algo
  
      def __iter__(self):
          return iter(asdict(self))
  
  
  @dataclass()
  class PubSet:
      """
      Prefix's public key set (list) at rotation index ridx
      """
      pubs: list = field(default_factory=list)  # list qb64 public keys.
  
      def __iter__(self):
          return iter(asdict(self))
  
  
  def riKey(pre, ri):
      """
      Returns bytes DB key from concatenation with '.' of qualified Base64 prefix
      bytes pre and int ri (rotation index) of key rotation.
      Inception has ri == 0
      """
      if hasattr(pre, "encode"):
          pre = pre.encode("utf-8")  # convert str to bytes
      return (b'%s.%032x' % (pre, ri))
  
  
  def openKS(name="test", **kwa):
      """
      Returns contextmanager generated by openLMDB but with Keeper instance as
      KeyStore
      default name="test"
      default temp=True,
  
      openLMDB Parameters:
          cls is Class instance of subclass instance
          name is str name of LMDBer dirPath so can have multiple databasers
              at different directory path names thar each use different name
          temp is Boolean, True means open in temporary directory, clear on close
              Otherwise open in persistent directory, do not clear on close
      """
      return dbing.openLMDB(cls=Keeper, name=name, **kwa)
  
  
  class Keeper(dbing.LMDBer):
      """
      Keeper sets up named sub databases for key pair storage (KS).
      Methods provide key pair creation, storage, and data signing.
  
       Attributes:  (inherited)
          name (str): unique path component used in directory or file path name
          base (str): another unique path component inserted before name
          temp (bool): True means use /tmp directory
          headDirPath is head directory path
          path is full directory path
          perm is numeric os permissions for directory and/or file(s)
          filed (bool): True means .path ends in file.
                         False means .path ends in directory
          mode (str): file open mode if filed
          fext (str): file extension if filed
          file (File)
          opened is Boolean, True means directory created and if file then file
                  is opened. False otherwise
          env (lmdb.env): LMDB main (super) database environment
          readonly (bool): True means open LMDB env as readonly
  
      Attributes:
          gbls (subing.Suber): named sub DB whose values are global parameters
              for all prefixes
              Key is parameter labels
              Value is parameter
                 parameters:
                     aeid (bytes): fully qualified qb64 non-transferable identifier
                         prefix for authentication via signing and asymmetric encryption
                         of secrets using the associated (public, private) key pair.
                         Secrets include both salts and private keys for all key sets
                         in keeper. Defaults to empty which means no authentication
                         or encryption of key sets.
                     pidx (bytes): hex index of next prefix key-pair sequence to be incepted
                     algo (str): default root algorithm for generating key pair
                     salt (bytes): root salt for generating key pairs
                     tier (bytes): default root security tier for root salt
  
          pris (subing.CryptSignerSuber): named sub DB whose keys are public key
              from key pair and values are private keys from key pair
              Key is public key (fully qualified qb64)
              Value is private key (fully qualified qb64)
          pres (subing.CesrSuber): named sub DB whose values are prefixes or first
              public keys
              Key is first public key in key sequence for a prefix (fully qualified qb64)
              Value is prefix or first public key (temporary) (fully qualified qb64
          prms (koming.Komer): named sub DB whose values are serialized dicts of
              PrePrm instance
              Key is identifier prefix (fully qualified qb64)
              Value is  serialized parameter dict (JSON) of public key parameters
              {
                  pidx: ,
                  algo: ,
                  salt: ,
                  stem: ,
                  tier: ,
              }
          sits (koming.Komer): named sub DB whose values are serialized dicts of
              PreSit instance
              Key is identifer prefix (fully qualified qb64)
              Value is  serialized parameter dict (JSON) of public key situation
                  {
                    old: { pubs: ridx:, kidx,  dt:},
                    new: { pubs: ridx:, kidx:, dt:},
                    nxt: { pubs: ridx:, kidx:, dt:}
                  }
          .pubs (koming.Komer): named sub DB whose values are serialized lists of
              public keys
              Enables lookup of public keys from prefix and ridx for replay of
              public keys by prefix in establishment event order.
              Key is prefix.ridx (rotation index as 32 char hex string)
                  use riKey(pre, ri)
              Value is serialized list of fully qualified public keys that are the
                  current signing keys after the rotation given by rotation index
  
      Properties:
  
  
      """
      TailDirPath = "keri/ks"
      AltTailDirPath = ".keri/ks"
      TempPrefix = "keri_ks_"
      MaxNamedDBs = 8
  
      def __init__(self, headDirPath=None, perm=None, reopen=False, **kwa):
          """
          Setup named sub databases.
  
          Inherited Parameters:
              name is str directory path name differentiator for main database
                  When system employs more than one keri database, name allows
                  differentiating each instance by name
                  default name='main'
              temp is boolean, assign to .temp
                  True then open in temporary directory, clear on close
                  Othewise then open persistent directory, do not clear on close
                  default temp=False
              headDirPath is optional str head directory pathname for main database
                  If not provided use default .HeadDirpath
                  default headDirPath=None so uses self.HeadDirPath
              perm is numeric optional os dir permissions mode
                  default perm=None so do not set mode
              reopen is boolean, IF True then database will be reopened by this init
                  default reopen=True
  
          Notes:
  
          dupsort=True for sub DB means allow unique (key,pair) duplicates at a key.
          Duplicate means that is more than one value at a key but not a redundant
          copies a (key,value) pair per key. In other words the pair (key,value)
          must be unique both key and value in combination.
          Attempting to put the same (key,value) pair a second time does
          not add another copy.
  
          Duplicates are inserted in lexocographic order by value, insertion order.
  
          """
          if perm is None:
              perm = self.Perm  # defaults to restricted permissions for non temp
  
          super(Keeper, self).__init__(headDirPath=headDirPath, perm=perm,
                                       reopen=reopen, **kwa)
  
      def reopen(self, **kwa):
          """
          Open sub databases
          """
          opened = super(Keeper, self).reopen(**kwa)
  
          # Create by opening first time named sub DBs within main DB instance
          # Names end with "." as sub DB name must include a non Base64 character
          # to avoid namespace collisions with Base64 identifier prefixes.
  
          self.gbls = subing.Suber(db=self, subkey='gbls.')
          self.pris = subing.CryptSignerSuber(db=self, subkey='pris.')
          self.pres = subing.CesrSuber(db=self,
                                         subkey='pres.',
                                         klas=coring.Prefixer)
          self.prms = koming.Komer(db=self,
                                   subkey='prms.',
                                   schema=PrePrm,)  # New Prefix Parameters
          self.sits = koming.Komer(db=self,
                                   subkey='sits.',
                                   schema=PreSit,)  # Prefix Situation
          self.pubs = koming.Komer(db=self,
                                   subkey='pubs.',
                                   schema=PubSet,)  # public key set at pre.ridx
          return self.opened
  
  
  class KeeperDoer(doing.Doer):
      """
      Basic Keeper Doer ( LMDB Database )
  
      Inherited Attributes:
          .done is Boolean completion state:
              True means completed
              Otherwise incomplete. Incompletion maybe due to close or abort.
  
      Attributes:
          .keeper is Keeper or LMDBer subclass
  
      Inherited Properties:
          .tyme is float relative cycle time of associated Tymist .tyme obtained
              via injected .tymth function wrapper closure.
          .tymth is function wrapper closure returned by Tymist .tymeth() method.
              When .tymth is called it returns associated Tymist .tyme.
              .tymth provides injected dependency on Tymist tyme base.
          .tock is float, desired time in seconds between runs or until next run,
                   non negative, zero means run asap
  
      Properties:
  
      Methods:
          .wind  injects ._tymth dependency from associated Tymist to get its .tyme
          .__call__ makes instance callable
              Appears as generator function that returns generator
          .do is generator method that returns generator
          .enter is enter context action method
          .recur is recur context action method or generator method
          .exit is exit context method
          .close is close context method
          .abort is abort context method
  
      Hidden:
          ._tymth is injected function wrapper closure returned by .tymen() of
              associated Tymist instance that returns Tymist .tyme. when called.
          ._tock is hidden attribute for .tock property
      """
  
      def __init__(self, keeper, **kwa):
          """
  
          Parameters:
             keeper is Keeper instance
          """
          super(KeeperDoer, self).__init__(**kwa)
          self.keeper = keeper
  
  
      def enter(self):
          """"""
          if not self.keeper.opened:
              self.keeper.reopen()
  
  
      def exit(self):
          """"""
          self.keeper.close(clear=self.keeper.temp)
  
  
  class Creator:
      """
      Class for creating a key pair based on algorithm.
  
      Attributes:
  
      Properties:
  
      Methods:
          .create is method to create key pair
  
      Hidden:
  
      """
  
      def __init__(self, **kwa):
          """
          Setup Creator.
  
          Parameters:
  
          """
  
      def create(self, **kwa):
          """
          Returns tuple of signers one per key pair
          """
          return []
  
      @property
      def salt(self):
          """
          salt property getter
          """
          return ''
  
      @property
      def stem(self):
          """
          stem property getter
          """
          return ''
  
      @property
      def tier(self):
          """
          tier property getter
          """
          return ''
  
  
  class RandyCreator(Creator):
      """
      Class for creating a key pair based on re-randomizing each seed algorithm.
  
      Attributes:
  
      Properties:
  
      Methods:
          .create is method to create key pair
  
      Hidden:
  
      """
  
      def __init__(self, **kwa):
          """
          Setup Creator.
  
          Parameters:
  
          """
          super(RandyCreator, self).__init__(**kwa)
  
      def create(self, codes=None, count=1, code=coring.MtrDex.Ed25519_Seed,
                 transferable=True, **kwa):
          """
          Returns list of signers one per kidx in kidxs
  
          Parameters:
              codes is list of derivation codes one per key pair to create
              count is count of key pairs to create is codes not provided
              code is derivation code to use for count key pairs if codes not provided
              transferable is Boolean, True means use trans deriv code. Otherwise nontrans
          """
          signers = []
          if not codes:  # if not codes make list len count of same code
              codes = [code for i in range(count)]
  
          for code in codes:
              signers.append(coring.Signer(code=code, transferable=transferable))
          return signers
  
  
  class SaltyCreator(Creator):
      """
      Class for creating a key pair based on random salt plus path stretch algorithm.
  
      Attributes:
          .salter is salter instance
  
      Properties:
  
  
      Methods:
          .create is method to create key pair
  
      Hidden:
          ._salter holds instance for .salter property
      """
  
      def __init__(self, salt=None, stem=None, tier=None, **kwa):
          """
          Setup Creator.
  
          Parameters:
              salt is unique salt from which to derive private key
              stem is path modifier wsed with salt to derive private keys.
                      if stem is None then uses pidx
              tier is derivation criticality that determines how much hashing to use.
  
          """
          super(SaltyCreator, self).__init__(**kwa)
          self.salter = coring.Salter(qb64=salt, tier=tier)
          self._stem = stem if stem is not None else ''
  
      @property
      def salt(self):
          """
          salt property getter
          """
          return self.salter.qb64
  
      @property
      def stem(self):
          """
          stem property getter
          """
          return self._stem
  
      @property
      def tier(self):
          """
          tier property getter
          """
          return self.salter.tier
  
      def create(self, codes=None, count=1, code=coring.MtrDex.Ed25519_Seed,
                 pidx=0, ridx=0, kidx=0, transferable=True, temp=False, **kwa):
          """
          Returns list of signers one per kidx in kidxs
  
          Parameters:
              codes is list of derivation codes one per key pair to create
              count is count of key pairs to create is codes not provided
              code is derivation code to use for count key pairs if codes not provided
              pidx is int prefix index for key pair sequence
              ridx is int rotation index for key pair set
              kidx is int starting key index for key pair set
              transferable is Boolean, True means use trans deriv code. Otherwise nontrans
              temp is Boolean True means use temp level for testing
          """
          signers = []
          if not codes:  # if not codes make list len count of same code
              codes = [code for i in range(count)]
  
          stem = self.stem if self.stem else "{:x}".format(pidx)  # if not stem use pidx
          for i, code in enumerate(codes):
              path = "{}{:x}{:x}".format(stem, ridx, kidx + i)
              signers.append(self.salter.signer(path=path,
                                                code=code,
                                                transferable=transferable,
                                                tier=self.tier,
                                                temp=temp))
          return signers
  
  
  class Creatory:
      """
      Factory class for creating Creator subclasses to create key pairs based on
      the provided algorithm.
  
      Usage: creator = Creatory(algo='salty').make(salt=b'0123456789abcdef')
  
      Attributes:
  
      Properties:
  
      Methods:
          .create is method to create key pair
  
      Hidden:
          ._create is method reference set to one of algorithm methods
          ._novelCreate
          ._indexCreate
      """
  
      def __init__(self, algo=Algos.salty):
          """
          Setup Creator.
  
          Parameters:
              algo is str code for algorithm
  
          """
          if algo == Algos.randy:
              self._make = self._makeRandy
          elif algo == Algos.salty:
              self._make = self._makeSalty
          else:
              raise ValueError("Unsupported creation algorithm ={}.".format(algo))
  
      def make(self, **kwa):
          """
          Returns Creator subclass based on inited algo
          """
          return (self._make(**kwa))
  
  
      def _makeRandy(self, **kwa):
          """
          """
          return RandyCreator(**kwa)
  
  
      def _makeSalty(self, **kwa):
          """
          """
          return SaltyCreator(**kwa)
  
  
  # default values to init manager's globals database
  Initage = namedtuple("Initage", 'aeid pidx salt tier')
  
  
  class Manager:
      """
      Class for managing key pair creation, storage, retrieval, and message signing.
  
      Attributes:
          ks (Keeper): key store LMDB database instance for storing public and private keys
          encrypter (coring.Encrypter): instance for encrypting secrets. Public
              encryption key is derived from aeid (public signing key)
          decrypter (coring.Decrypter): instance for decrypting secrets. Private
              decryption key is derived seed (private signing key seed)
          inited (bool): True means fully initialized wrt database.
                            False means not yet fully initialized
  
      Attributes (Hidden):
  
          _seed (str): qb64 private-signing key (seed) for the aeid from which
                  the private decryption key is derived. If aeid stored in
                  database is not empty then seed may required to do any key
                  management operations. The seed value is memory only and MUST NOT
                  be persisted to the database for the manager with which it is used.
                  It MUST only be loaded once when the process that runs the Manager
                  is initialized. Its presence acts as an authentication, authorization,
                  and decryption secret for the Manager and must be stored on
                  another device from the device that runs the Manager.
  
  
      Properties:
          aeid (str): authentication and encryption fully qualified qb64
              non-transferable identifier prefix for authentication via signing
              and asymmetric encryption of secrets using the associated
              (public, private) key pair. Secrets include both salts and private
              keys for all key sets in keeper. Defaults to empty which means no
              authentication or encryption of key sets. Use initial attribute because
              keeper may not be open on init.
  
          pidx (int): initial pidx prefix index. Use initial attribute because keeper
              may not be open on init.
  
          salt (str): initial salt. Use inital attribute because keeper may not be
               open on init.
  
          tier (str): initial security tier as value of Tierage. Use initial attribute
              because keeper may not be open on init
  
      Methods:
  
      """
  
      def __init__(self, *, ks=None, seed=None, **kwa):
          """
          Setup Manager.
  
          Parameters:
              ks (Keeper): key store instance (LMDB)
              seed (str): qb64 private-signing key (seed) for the aeid from which
                  the private decryption key may be derived. If aeid stored in
                  database is not empty then seed may required to do any key
                  management operations. The seed value is memory only and MUST NOT
                  be persisted to the database for the manager with which it is used.
                  It MUST only be loaded once when the process that runs the Manager
                  is initialized. Its presence acts as an authentication, authorization,
                  and decryption secret for the Manager and must be stored on
                  another device from the device that runs the Manager.
  
          Parameters: Passthrough to .setup for later initialization
              aeid (str): qb64 of non-transferable identifier prefix for
                  authentication and encryption of secrets in keeper. If provided
                  aeid (not None) and different from aeid stored in database then
                  all secrets are re-encrypted using new aeid. In this case the
                  provided prikey must not be empty. A change in aeid should require
                  a second authentication mechanism besides the prikey.
              pidx (int): index of next new created key pair sequence for given
                  identifier prefix
              salt (str): qb64 of root salt. Makes random root salt if not provided
              tier (str): default security tier (Tierage) for root salt
          """
          self.ks = ks if ks is not None else Keeper(reopen=True)
          self.encrypter = None
          self.decrypter = None
          self._seed = seed if seed is not None else ""
          self.inited = False
  
          # save keyword arg parameters to init later if db not opened yet
          self._inits = kwa
  
          if self.ks.opened:  # allows keeper db to opened asynchronously
              self.setup(**self._inits)  # first call to .setup with initialize database
  
  
      def setup(self, aeid=None, pidx=None, algo=None, salt=None, tier=None):
          """
          Setups manager root or global attributes and properties
          Assumes that .keeper db is open.
          If .keeper.gbls sub database has not been initialized for the first time
          then initializes from ._inits. This allows dependency injection of
          keepr db into manager instance prior to keeper db being opened to
          accomodate asynchronous process setup of db resources. Putting the db
          initialization here enables asynchronous opening of keeper db after
          keeper instance is instantiated. First call to .setup will initialize
          keeper db defaults if never before initialized (vacuous initialization).
  
          Parameters:
              aeid (str): qb64 of non-transferable identifier prefix for
                  authentication and encryption of secrets in keeper. If provided
                  aeid (not None) and different from aeid stored in database then
                  all secrets are re-encrypted using new aeid. In this case the
                  provided prikey must not be empty. A change in aeid should require
                  a second authentication mechanism besides the prikey.
              pidx (int): index of next new created key pair sequence for given
                  identifier prefix
              algo (str): root algorithm (randy or salty) for creating key pairs
              salt (str): qb64 of root salt. Makes random root salt if not provided
              tier (str): default security tier (Tierage) for root salt
  
          """
          if not self.ks.opened:
              raise kering.ClosedError("Attempt to setup Manager closed keystore"
                                       " database .ks.")
  
          if aeid is None:
              aeid = ''
          if pidx is None:
              pidx = 0
          if algo is None:
              algo = Algos.salty
          if salt is None:
              salt = coring.Salter().qb64
          if tier is None:
              tier = coring.Tiers.low
  
          # update  database if never before initialized
          if self.pidx is None:  # never before initialized
              self.pidx = pidx  # init to default
  
          if self.algo is None:  # never before initialized
              self.algo = algo
  
          if self.salt is None:  # never before initialized
              self.salt = salt
  
          if self.tier is None:  # never before initialized
              self.tier = tier  # init to default
  
  
          # must do this after salt is initialized so gets re-encrypted correctly
          if self.aeid is None:  # never before initialized
              self.updateAeid(aeid, self.seed)
  
          self.inited = True
  
  
      def updateAeid(self, aeid, seed):
          """
          Given seed belongs to aeid and encrypter, update aeid and re-encrypt all
          secrets
  
          Parameters:
              aeid (str): qb64 of new auth encrypt id  (public signing key)
              seed (str): qb64 of new seed from which new aeid is derived (private signing
                          key seed)
          """
          if self.aeid:  # check that last current seed matches last current .aeid
              # verifies seed belongs to aeid
              if not self.seed or not self.encrypter.verifySeed(self.seed):
                  raise kering.AuthError("Last seed missing or provided last seed "
                                         "not associated with last aeid={}."
                                         "".format(self.aeid))
  
          if aeid:  # aeid provided
              if aeid != self.aeid:  # changing to a new aeid so update .encrypter
                  self.encrypter = coring.Encrypter(verkey=aeid)  # derive encrypter from aeid
                  # verifies new seed belongs to new aeid
                  if not seed or not self.encrypter.verifySeed(seed):
                      raise kering.AuthError("Seed missing or provided seed not associated"
                                                 "  with provided aeid={}.".format(aeid))
          else:  # changing to empty aeid so new encrypter is None
              self.encrypter = None
  
          # fetch all secrets from db, decrypt all secrets with self.decrypter
          # unless they decrypt automatically on fetch and then re-encrypt with
          # encrypter  update db with re-encrypted values
  
          # re-encypt root salt secret, .salt property is automatically decrypted on fetch
          if (salt := self.salt) is not None:  # decrypted salt
              self.salt = salt
              # self.salt = self.encrypter.encrypt(ser=salt).qb64 if self.encrypter else salt
  
          # other secrets
          if self.decrypter:
              # re-encrypt root salt secrets by prefix parameters .prms
              for keys, data in self.ks.prms.getItemIter():  # keys is tuple of pre qb64
                  if data.salt:
                      salter = self.decrypter.decrypt(ser=data.salt)
                      data.salt = (self.encrypter.encrypt(matter=salter).qb64
                                   if self.encrypter else salter.qb64)
                      self.ks.prms.pin(keys, val=data)
  
              # private signing key seeds
              # keys is tuple == (verkey.qb64,) .pris database auto decrypts
              for keys, signer in self.ks.pris.getItemIter(decrypter=self.decrypter):
                  self.ks.pris.pin(keys, signer, encrypter=self.encrypter)
  
          self.ks.gbls.pin("aeid", aeid)  # set aeid in db
          self._seed = seed  # set .seed in memory
  
          # update .decrypter
          self.decrypter = coring.Decrypter(seed=seed) if seed else None
  
  
      @property
      def seed(self):
          """
          seed property getter from ._seed.
          seed (str): qb64 from which aeid is derived
          """
          return self._seed
  
  
      @property
      def aeid(self):
          """
          aeid property getter from key store db.
          Assumes db initialized.
          aeid is qb64 auth encrypt id prefix
          """
          return self.ks.gbls.get('aeid')
  
  
      @property
      def pidx(self):
          """
          pidx property getter from key store db.
          Assumes db initialized.
          pidx is prefix index int for next new key sequence
          """
          if (pidx := self.ks.gbls.get("pidx")) is not None:
              return int(pidx, 16)
          return pidx  # None
  
  
      @pidx.setter
      def pidx(self, pidx):
          """
          pidx property setter to key store db.
          pidx is prefix index int for next new key sequence
          """
          self.ks.gbls.pin("pidx", "%x" % pidx)
  
  
      @property
      def algo(self):
          """
          also property getter from key store db.
          Assumes db initialized.
          algo is default root algorithm for creating key pairs
          """
          return self.ks.gbls.get('algo')
  
  
      @algo.setter
      def algo(self, algo):
          """
          algo property setter to key store db.
          algo is default root algorithm for creating key pairs
          """
          self.ks.gbls.pin('algo', algo)
  
  
      @property
      def salt(self):
          """
          salt property getter from key store db.
          Assumes db initialized.
          salt is default root salt for new key sequence creation
          """
          salt = self.ks.gbls.get('salt')
          if self.decrypter:  # given .decrypt secret salt must be encrypted in db
              return self.decrypter.decrypt(ser=salt).qb64
          return salt
  
  
      @salt.setter
      def salt(self, salt):
          """
          salt property setter to key store db.
          Parameters:
              salt (str): qb64 default root salt for new key sequence creation
                  may be plain text or cipher text handled by updateAeid
          """
          if self.encrypter:
              salt = self.encrypter.encrypt(ser=salt).qb64
          self.ks.gbls.pin('salt', salt)
  
  
      @property
      def tier(self):
          """
          tier property getter from key store db.
          Assumes db initialized.
          tier is default root security tier for new key sequence creation
          """
          return self.ks.gbls.get('tier')
  
  
      @tier.setter
      def tier(self, tier):
          """
          tier property setter to key store db.
          tier is default root security tier for new key sequence creation
          """
          self.ks.gbls.pin('tier', tier)
  
  
      def incept(self, icodes=None, icount=1, icode=coring.MtrDex.Ed25519_Seed, isith=None,
                       ncodes=None, ncount=1, ncode=coring.MtrDex.Ed25519_Seed, nsith=None,
                       dcode=coring.MtrDex.Blake3_256,
                       algo=None, salt=None, stem=None, tier=None, rooted=True,
                       transferable=True, temp=False):
          """
          Returns tuple (verfers, digers, cst, nst) for inception event where
              verfers is list of current public key verfers
                  public key is verfer.qb64
              digers is list of next public key digers
                  digest to xor is diger.raw
              cst is current signing threshold for verfers for Tholder
              nst is next signing threshold for digers for Tholder or Nexter
  
          Incept a prefix. Use first public key as temporary prefix.
          Must .repre later to move pubsit dict to correct permanent prefix.
          Store the dictified PreSit in the keeper under the first public key
  
  
          Parameters:
              icodes is list of private key derivation codes qb64 str
                  one per incepting key pair
              icount is int count of incepting public keys when icodes not provided
              icode is str derivation code qb64  of all icount incepting private keys
                  when icodes list not provided
              isith is incepting signing threshold as:
                  int, str hex, or list of weights
              ncodes is list of private key derivation codes qb64 str
                  one per next key pair
              ncount is int count of next public keys when ncodes not provided
              ncode is str derivation code qb64  of all ncount next public keys
                  when ncodes not provided
              nsith is next singning threshold as:
                  int, str hex, or list of weights
              dcode is str derivation code qb64 of next digers. Default is MtrDex.Blake3_256
              algo is str key creation algorithm code
              salt is str qb64 salt for randomization when salty algorithm used
              stem is path modifier used with salt to derive private keys when using
                  salty agorithms. if stem is None then uses pidx
              tier is str security criticality tier code when using salty algorithm
              rooted is Boolean true means derive incept salt from root salt when
                  incept salt not provided. Otherwise use incept salt only
              transferable is Boolean, True means each public key uses transferable
                  derivation code. Default is transferable. Special case is non-transferable
                  Use case for incept to use transferable = False is for basic
                  derivation of non-transferable identifier prefix.
                  When the derivation process of the identifier prefix is
                  transferable then one should not use non-transferable for the
                  associated public key(s).
              temp is Boolean. True is temporary for testing. It modifies tier of salty algorithm
  
          When both ncodes is empty and ncount is 0 then the nxt is null and will
              not be rotatable. This makes the identifier non-transferable in effect
              even when the identifer prefix is transferable.
  
          """
          # get root defaults to initialize key sequence
          if rooted and algo is None:  # use root algo from db as default
              algo = self.algo
  
          if rooted and salt is None:  # use root salt from db instead of random salt
              salt = self.salt
  
          if rooted and tier is None:  # use root tier from db as default
              tier = self.tier
  
          pidx = self.pidx  # get next pidx
          ridx = 0  # rotation index
          kidx = 0  # key pair index
  
          creator = Creatory(algo=algo).make(salt=salt, stem=stem, tier=tier)
  
          if not icodes:  # all same code, make list of len icount of same code
              if icount <= 0:
                  raise ValueError("Invalid icount={} must be > 0.".format(icount))
              icodes = [icode for i in range(icount)]
  
          isigners = creator.create(codes=icodes,
                                    pidx=pidx, ridx=ridx, kidx=kidx,
                                    transferable=transferable, temp=temp)
          verfers = [signer.verfer for signer in isigners]
  
          if isith is None:
              isith = "{:x}".format(max(1, math.ceil(len(isigners) / 2)))
          cst = coring.Tholder(sith=isith).sith  # current signing threshold
  
          if not ncodes:  # all same code, make list of len ncount of same code
              if ncount < 0:  # next may be zero if non-trans
                  raise ValueError("Invalid ncount={} must be >= 0.".format(ncount))
              ncodes = [ncode for i in range(ncount)]
  
          # count set to 0 to ensure does not create signers if ncodes is empty
          nsigners = creator.create(codes=ncodes, count=0,
                                    pidx=pidx, ridx=ridx+1, kidx=kidx+len(icodes),
                                    transferable=transferable, temp=temp)
          digers = [coring.Diger(ser=signer.verfer.qb64b, code=dcode) for signer in nsigners]
  
          if nsith is None:
              nsith = "{:x}".format(max(0, math.ceil(len(nsigners) / 2)))
          nst = coring.Tholder(sith=nsith).sith  # next signing threshold
  
          # Secret to encrypt here
          pp = PrePrm(pidx=pidx,
                      algo=algo,
                      salt=(creator.salt if not self.encrypter
                            else self.encrypter.encrypt(ser=creator.salt).qb64),
                      stem=creator.stem,
                      tier=creator.tier)
  
          dt = helping.nowIso8601()
          ps = PreSit(
                      new=PubLot(pubs=[verfer.qb64 for verfer in verfers],
                                     ridx=ridx, kidx=kidx, st=cst, dt=dt),
                      nxt=PubLot(pubs=[signer.verfer.qb64 for signer in nsigners],
                                     ridx=ridx+1, kidx=kidx+len(icodes), st=nst, dt=dt))
  
          pre = verfers[0].qb64b
          if not self.ks.pres.put(pre, val=coring.Prefixer(qb64=pre)):
              raise ValueError("Already incepted pre={}.".format(pre.decode("utf-8")))
  
          if not self.ks.prms.put(pre, val=pp):
              raise ValueError("Already incepted prm for pre={}.".format(pre.decode("utf-8")))
  
          self.pidx = pidx + 1  # increment for next inception
  
          if not self.ks.sits.put(pre, val=ps):
              raise ValueError("Already incepted sit for pre={}.".format(pre.decode("utf-8")))
  
          for signer in isigners:  # store secrets (private key val keyed by public key)
              self.ks.pris.put(keys=signer.verfer.qb64b, val=signer,
                               encrypter=self.encrypter)
  
          self.ks.pubs.put(riKey(pre, ri=ridx), val=PubSet(pubs=ps.new.pubs))
  
          for signer in nsigners:  # store secrets (private key val keyed by public key)
              self.ks.pris.put(keys=signer.verfer.qb64b, val=signer,
                               encrypter=self.encrypter)
  
          # store publics keys for lookup of private key for replay
          self.ks.pubs.put(riKey(pre, ri=ridx+1), val=PubSet(pubs=ps.nxt.pubs))
  
          return (verfers, digers, cst, nst)
  
  
      def move(self, old, new):
          """
          Assigns new pre to old default .pres at old
  
          Moves PrePrm and PreSit dicts in keeper db from old default pre to new pre db key
          The new pre is the newly derived prefix which may only be known some
          time after the original creation of the associated key pairs.
  
          Paraameters:
             old is str for old prefix of pubsit dict in keeper db
             new is str for new prefix to move pubsit dict to in keeper db
          """
          if old == new:
              return
  
          if self.ks.pres.get(old) is None:
              raise ValueError("Nonexistent old pre={}, nothing to assign.".format(old))
  
          if self.ks.pres.get(new) is not None:
              raise ValueError("Preexistent new pre={} may not clobber.".format(new))
  
          if (oldprm := self.ks.prms.get(old)) is None:
              raise ValueError("Nonexistent old prm for pre={}, nothing to move.".format(old))
  
          if self.ks.prms.get(new) is not None:
              raise ValueError("Preexistent new prm for pre={} may not clobber.".format(new))
  
          if (oldsit := self.ks.sits.get(old)) is None:
              raise ValueError("Nonexistent old sit for pre={}, nothing to move.".format(old))
  
          if self.ks.sits.get(new) is not None:
              raise ValueError("Preexistent new sit for pre={} may not clobber.".format(new))
  
          if not self.ks.prms.put(new, val=oldprm):
              raise ValueError("Failed moving prm from old pre={} to new pre={}.".format(old, new))
          else:
              self.ks.prms.rem(old)
  
          if not self.ks.sits.put(new, val=oldsit):
              raise ValueError("Failed moving sit from old pre={} to new pre={}.".format(old, new))
          else:
              self.ks.sits.rem(old)
  
          # move .pubs entries if any
          i = 0
          while (pl := self.ks.pubs.get(riKey(old, i))):
              if not self.ks.pubs.put(riKey(new, i), val=pl):
                  raise ValueError("Failed moving pubs at pre={} ri={} to new"
                                   " pre={}".format(old, i, new))
              i += 1
  
          # assign old
          if not self.ks.pres.pin(old, val=coring.Prefixer(qb64=new)):
              raise ValueError("Failed assiging new pre={} to old pre={}.".format(new, old))
  
          # make new so that if move again we reserve each one
          if not self.ks.pres.put(new, val=coring.Prefixer(qb64=new)):
              raise ValueError("Failed assiging new pre={}.".format(new))
  
  
      def rotate(self, pre, codes=None, count=1, code=coring.MtrDex.Ed25519_Seed,
                       sith=None, dcode=coring.MtrDex.Blake3_256,
                       transferable=True, temp=False, erase=True):
          """
          Returns tuple (verfers, digers, cst, nst) for rotation event of keys for pre where
              verfers is list of current public key verfers
                  public key is verfer.qb64
              digers is list of next public key digers
                  digest to xor is diger.raw
              cst is current signing threshold for verfers for Tholder
              nst is next signing threshold fo digers for Tholder or Nexter
  
          Rotate a prefix.
          Store the updated dictified PreSit in the keeper under pre
  
          Parameters:
              pre is str qb64 of prefix
              codes is list of private key derivation codes qb64 str
                  one per next key pair
              count is int count of next public keys when icodes not provided
              code is str derivation code qb64  of all ncount next public keys
                  when ncodes not provided
              sith is next signing threshold as:
                  int, str hex, or list of weights
              dcode is str derivation code qb64 of digers. Default is MtrDex.Blake3_256
              transferable is Boolean, True means each public key uses transferable
                  derivation code. Default is transferable. Special case is non-transferable
                  Normally no use case for rotation to use transferable = False.
                  When the derivation process of the identifier prefix is
                  transferable then one should not use transferable = False for the
                  associated public key(s).
              temp is Boolean. True is temporary for testing. It modifies tier of salty algorithm
              erase is Boolean. True means erase old private keys made stale by rotation
  
          When both ncodes is empty and ncount is 0 then the nxt is null and will
              not be rotatable. This makes the identifier non-transferable in effect
              even when the identifer prefix is transferable.
  
          """
          # Secret to decrypt here
          if (pp := self.ks.prms.get(pre)) is None:
              raise ValueError("Attempt to rotate nonexistent pre={}.".format(pre))
  
          if (ps := self.ks.sits.get(pre)) is None:
              raise ValueError("Attempt to rotate nonexistent pre={}.".format(pre))
  
          if not ps.nxt.pubs:  # empty nxt public keys so non-transferable prefix
              raise ValueError("Attempt to rotate nontransferable pre={}.".format(pre))
  
          old = ps.old  # save old so can clean out if rotate successful
          ps.old = ps.new  # move new to old
          ps.new = ps.nxt  # move nxt to new
  
          verfers = []  # assign verfers from new nxt was old nxt now new nxt.
          for pub in ps.new.pubs:  # maybe should rethink this
              if self.aeid and not self.decrypter:
                  raise kering.DecryptError("Unauthorized decryption attempt. "
                                            "Aeid but no decrypter.")
  
              if ((signer := self.ks.pris.get(pub.encode("utf-8"),
                                             decrypter=self.decrypter)) is None):
                  raise ValueError("Missing prikey in db for pubkey={}".format(pub))
              verfers.append(signer.verfer)
  
          cst = ps.new.st  # get new current signing threshold
  
          salt = pp.salt
          if salt:
              if self.aeid:
                  if not self.decrypter:
                      raise kering.DecryptError("Unauthorized decryption. Aeid but no decrypter.")
                  salt = self.decrypter.decrypt(ser=salt).qb64
              else:
                  salt = coring.Salter(qb64=salt).qb64  # ensures salt was unencrypted
  
          creator = Creatory(algo=pp.algo).make(salt=salt, stem=pp.stem, tier=pp.tier)
  
          if not codes:  # all same code, make list of len count of same code
              if count < 0:  # next may be zero if non-trans
                  raise ValueError("Invalid count={} must be >= 0.".format(count))
              codes = [code for i in range(count)]
  
          pidx = pp.pidx  # get pidx for this key sequence, may be used by salty creator
          ridx = ps.new.ridx + 1
          kidx = ps.nxt.kidx + len(ps.new.pubs)
  
          # count set to 0 to ensure does not create signers if codes is empty
          signers = creator.create(codes=codes, count=0,
                                   pidx=pidx, ridx=ridx, kidx=kidx,
                                   transferable=transferable, temp=temp)
          digers = [coring.Diger(ser=signer.verfer.qb64b, code=dcode) for signer in signers]
  
          if sith is None:
              sith = "{:x}".format(max(0, math.ceil(len(signers) / 2)))
          nst = coring.Tholder(sith=sith).sith  # next signing threshold
  
          dt = helping.nowIso8601()
          ps.nxt = PubLot(pubs=[signer.verfer.qb64 for signer in signers],
                                ridx=ridx, kidx=kidx, st=nst, dt=dt)
  
          if not self.ks.sits.pin(pre, val=ps):
              raise ValueError("Problem updating pubsit db for pre={}.".format(pre))
  
          for signer in signers:  # store secrets (private key val keyed by public key)
              self.ks.pris.put(keys=signer.verfer.qb64b, val=signer,
                               encrypter=self.encrypter)
  
          # store public keys for lookup of private keys by public key for replay
          self.ks.pubs.put(riKey(pre, ri=ps.nxt.ridx), val=PubSet(pubs=ps.nxt.pubs))
  
          if erase:
              for pub in old.pubs:  # remove old prikeys
                  self.ks.pris.rem(pub)
  
          return (verfers, digers, cst, nst)
  
  
      def sign(self, ser, pubs=None, verfers=None, indexed=True, indices=None):
          """
          Returns list of signatures of ser if indexed as Sigers else as Cigars with
          .verfer assigned.
  
          Parameters:
              ser is bytes serialization to sign
              pubs is list of qb64 public keys to lookup private keys
              verfers is list of Verfers for public keys
              indexed is Boolean, True means use offset into pubs/verfers/signers
                  for index and return Siger instances. False means return Cigar instances
              indices is list of int indexes (offsets) to use for indexed signatures
                  that may differ from the order of appearance in the pubs or verfers
                  lists. This allows witness indexed sigs or controller multi-sig
                  where the parties do not share the same manager or ordering so
                  the default ordering in pubs or verfers is wrong for the index.
                  If provided the length of indices must match pubs/verfers/signers
                  else raises ValueError. If not provided and indexed is True then use
                  default index that is offset into pubs/verfers/signers
  
  
          if neither pubs or verfers provided then returns empty list of signatures
          If pubs then ignores verfers otherwise uses verferss
  
          Manager implement .sign method and tests
          sign(self,ser,pubs,indexed=True)
          checks for pris for pubs in db is not raises error
          then signs ser with eah pub
          returns list of sigers indexed else list of cigars if not
          """
          signers = []
  
          if pubs is None and verfers is None:
              raise ValueError("pubs or verfers required")
  
          if pubs:
              for pub in pubs:
                  if self.aeid and not self.decrypter:
                      raise kering.DecryptError("Unauthorized decryption attempt. "
                                                "Aeid but no decrypter.")
                  if ((signer := self.ks.pris.get(pub, decrypter=self.decrypter))
                          is None):
                      raise ValueError("Missing prikey in db for pubkey={}".format(pub))
                  signers.append(signer)
  
          else:
              for verfer in verfers:
                  if self.aeid and not self.decrypter:
                      raise kering.DecryptError("Unauthorized decryption attempt. "
                                                "Aeid but no decrypter.")
                  if ((signer := self.ks.pris.get(verfer.qb64,
                                                  decrypter=self.decrypter))
                          is None):
                      raise ValueError("Missing prikey in db for pubkey={}".format(verfer.qb64))
                  signers.append(signer)
  
          if indices and len(indices) != len(signers):
              raise ValueError("Mismatch length indices={} and resultant signers "
                               "list={}".format(len(indices), len(signers)))
  
          if indexed or indices:
              sigers = []
              for i, signer in enumerate(signers):
                  if indices:
                      i = indices[i]  # get index from indices
                  sigers.append(signer.sign(ser, index=i))  # assigns .verfer to siger
              return sigers
          else:
              cigars = []
              for signer in signers:
                  cigars.append(signer.sign(ser))  # assigns .verfer to cigar
              return cigars
  
  
      def ingest(self, secrecies, ncount=1, ncode=coring.MtrDex.Ed25519_Seed,
                       dcode=coring.MtrDex.Blake3_256,
                       algo=Algos.salty, salt=None, stem=None, tier=None,
                       rooted=True, transferable=True, temp=False):
          """
          Ingest secrecies as a list of lists of secrets organized in event order
          to register the sets of secrets of associated externally generated keypair
          lists into the database.
          Returns tuple of (verferies, digers) where verferies is a list of lists
          of the corresponding public keys from secrecies and digers is the list
          of digers for the digest of the newly created next keys after the last
          entry in secrecies. Essentially ingest ends with the current keys as the
          last key list in secrecies and the nxt keys are newly created as if a
          rotation to the last set of keys was performed. Unlike rotate, however,
          ingest does not delete any of the private keys it ingests. This must be
          done separately if desired.
  
          Each list in secrecies is an ordered list of private keys corresponding
          to the public list in the key state for each establishment event in order.
          The first list are the keys for the inception event, the next list for
          the first rotation, and each subsequent list for the next rotation and
          so on.
  
          May be used for import or recovery from backup.
          Method parameters specify the policy for generating new keys pairs for
          rotations that follow the ingested list of lists. The parameters are used
          to define how torotate to new key pairs that follow the ingested sequence.
  
          Parameters:
              secrecies is list of lists of fully qualified secrets (private keys)
              ncount is int count of next public keys when ncodes not provided
              ncode is str derivation code qb64  of all ncount next public keys
                  when ncodes not provided
              dcode is str derivation code qb64 of digers. Default is MtrDex.Blake3_256
              algo is str key creation algorithm code
              salt is str qb64 salt for randomization when salty algorithm used
              stem is path modifier used with salt to derive private keys when using
                  salty agorithms. if stem is None then uses pidx
              tier is str security criticality tier code when using salty algorithm
              rooted is Boolean true means derive incept salt from root salt when
                  incept salt not provided. Otherwise use incept salt only
              transferable is Boolean, True means each public key uses transferable
                  derivation code. Default is transferable. Special case is non-transferable
                  Use case for incept to use transferable = False is for basic
                  derivation of non-transferable identifier prefix.
                  When the derivation process of the identifier prefix is
                  transferable then one should not use non-transferable for the
                  associated public key(s).
              temp is Boolean. True is temporary for testing. It modifies tier of salty algorithm
  
          """
          # configure parameters for creating new keys after ingested sequence
          if rooted and salt is None:  # use root salt instead of random salt
              salt = self.salt
  
          if rooted and tier is None:  # use root tier as default
              tier = self.tier
  
          pidx = self.pidx  # get next pidx
  
          creator = Creatory(algo=algo).make(salt=salt, stem=stem, tier=tier)
  
          dt = ""
          pubs = []
          oridx = 0
          okidx = 0
          cridx = 0
          ckidx = 0
          ridx = 0
          kidx = 0
          verferies = []  # list of lists of verfers
          first = True
          secrecies = deque(secrecies)
          while secrecies:
              csecrets = secrecies.popleft()  # current
              csigners = [coring.Signer(qb64=secret, transferable=transferable)
                                                        for secret in csecrets]
              csize = len(csigners)
              verferies.append([signer.verfer for signer in csigners])
  
              if first:
                  # Secret to encrypt here
                  pp = PrePrm(pidx=pidx,
                              algo=algo,
                              salt=(creator.salt if not self.encrypter
                                    else self.encrypter.encrypt(ser=creator.salt).qb64),
                              stem=creator.stem,
                              tier=creator.tier)
                  pre = csigners[0].verfer.qb64b
                  if not self.ks.pres.put(pre, val=coring.Prefixer(qb64=pre)):
                      raise ValueError("Already incepted pre={}.".format(pre.decode("utf-8")))
  
                  if not self.ks.prms.put(pre, val=pp):
                      raise ValueError("Already incepted prm for pre={}.".format(pre.decode("utf-8")))
  
                  self.pidx = pidx + 1  # increment for next inception
                  first = False
  
              for signer in csigners:  # store secrets (private key val keyed by public key)
                  self.ks.pris.put(keys=signer.verfer.qb64b, val=signer,
                                   encrypter=self.encrypter)
  
              self.ks.pubs.put(riKey(pre, ri=ridx),
                                  val=PubSet(pubs=[signer.verfer.qb64
                                          for signer in csigners]))
  
              odt = dt
              dt = helping.nowIso8601()
              opubs = pubs
              pubs = [signer.verfer.qb64 for signer in csigners]
              okidx = ckidx  # old kidx
              oridx = cridx  # old ridx
              ckidx = kidx  # current kidx
              cridx = ridx  # currrent ridx
              ridx += 1  # next ridx
              kidx += csize  # next kidx
  
  
          # create nxt signers after ingested signers
          nsigners = creator.create(count=ncount, code=ncode,
                                    pidx=pidx, ridx=ridx, kidx=kidx,
                                    transferable=transferable, temp=temp)
  
          digers = [coring.Diger(ser=signer.verfer.qb64b, code=dcode) for signer in nsigners]
  
          for signer in nsigners:  # store secrets (private key val keyed by public key)
              self.ks.pris.put(keys=signer.verfer.qb64b, val=signer)
  
          self.ks.pubs.put(riKey(pre, ri=ridx),
                               val=PubSet(pubs=[signer.verfer.qb64
                                                 for signer in nsigners]))
  
          csith = "{:x}".format(max(1, math.ceil(len(csigners) / 2)))
          cst = coring.Tholder(sith=csith).sith
  
          nsith = "{:x}".format(max(0, math.ceil(len(nsigners) / 2)))
          nst = coring.Tholder(sith=nsith).sith
  
          dt = helping.nowIso8601()
          old=PubLot(pubs=opubs, ridx=oridx, kidx=okidx, st='0', dt=odt)
          new=PubLot(pubs=[signer.verfer.qb64 for signer in csigners],
                             ridx=cridx, kidx=ckidx, st=cst, dt=dt)
          nxt=PubLot(pubs=[signer.verfer.qb64 for signer in nsigners],
                             ridx=ridx, kidx=kidx, st=nst, dt=dt)
  
          ps = PreSit(old=old, new=new, nxt=nxt)
          if not self.ks.sits.pin(pre, val=ps):
              raise ValueError("Problem updating pubsit db for pre={}.".format(pre))
          return (verferies, digers)
  
  
      def replay(self, pre, ridx=0, code=coring.MtrDex.Blake3_256, erase=True):
          """
          Returns duple (verfers, digers) associated with public key set from
          the key sequence for identifier prefix pre at rotation index ridx stored
          in db .pubs. Inception is at ridx == 0.
          Enables replay of preexisting public key sequence.
          In returned duple:
              verfers is list of current public key verfers
                  public key is verfer.qb64
              digers is list of next public key digers
                  digest to xor is diger.raw
  
          If key sequence at ridx does already exist in .pubs database for pre then
              raises ValueError.
          If  preexisting pubs for pre exist but .ridx is two large for preexisting
              pubs then raises IndexError.
  
          Parameters:
              pre is str fully qualified qb64 identifier prefix
              ridx is integer rotation index
              code is str derivation code for digers. Default is MtrDex.Blake3_256
  
          """
          oldps = None
          if ridx - 1 >= 0:
              oldps = self.ks.pubs.get(riKey(pre, ridx-1))
  
          newps = self.ks.pubs.get(riKey(pre, ridx))
          nxtps = self.ks.pubs.get(riKey(pre, ridx+1))
  
          if not (newps and nxtps):  # replay finished  #not (newpubs and nxtpubs)
              if self.ks.pubs.get(riKey(pre, ridx)):  # past replay but next pubs
                  # raises IndexError to indicate replay at ridx past end but valid
                  # next keys at ridx
                  raise IndexError("Invalid replay attempt at ridx={} for pubs of "
                                   "pre={}.".format(ridx, pre))
              else:  # past replay at ridx and no next keys at ridx
                  # raise ValueError to indicate replay at ridx is past end of replay
                  # and no valid next keys at ridx
                  raise ValueError("Invalid replay at ridx={} missing pubs for "
                                   "pre={}.".format(ridx, pre))
  
          if erase and oldps:
              for pub in oldps.pubs:  # remove old prikeys
                  self.ks.pris.rem(pub)
  
          verfers = [coring.Verfer(qb64=pub) for pub in newps.pubs]
          digers = [coring.Diger(ser=pub.encode("utf-8"), code=code) for pub in nxtps.pubs]
  
          csith = "{:x}".format(max(1, math.ceil(len(verfers) / 2)))
          cst = coring.Tholder(sith=csith).sith
  
          nsith = "{:x}".format(max(0, math.ceil(len(digers) / 2)))
          nst = coring.Tholder(sith=nsith).sith
  
          return (verfers, digers, cst, nst)
  
  
  class ManagerDoer(doing.Doer):
      """
      Basic Manager Doer to initialize keystore database .ks
  
      Inherited Attributes:
          .done is Boolean completion state:
              True means completed
              Otherwise incomplete. Incompletion maybe due to close or abort.
  
      Attributes:
          .manager is Manager subclass
  
      Inherited Properties:
          .tyme is float relative cycle time of associated Tymist .tyme obtained
              via injected .tymth function wrapper closure.
          .tymth is function wrapper closure returned by Tymist .tymeth() method.
              When .tymth is called it returns associated Tymist .tyme.
              .tymth provides injected dependency on Tymist tyme base.
          .tock is float, desired time in seconds between runs or until next run,
                   non negative, zero means run asap
  
      Properties:
  
      Methods:
          .wind  injects ._tymth dependency from associated Tymist to get its .tyme
          .__call__ makes instance callable
              Appears as generator function that returns generator
          .do is generator method that returns generator
          .enter is enter context action method
          .recur is recur context action method or generator method
          .exit is exit context method
          .close is close context method
          .abort is abort context method
  
      Hidden:
          ._tymth is injected function wrapper closure returned by .tymen() of
              associated Tymist instance that returns Tymist .tyme. when called.
          ._tock is hidden attribute for .tock property
      """
  
      def __init__(self, manager, **kwa):
          """
          Parameters:
             manager (Manager): instance
          """
          super(ManagerDoer, self).__init__(**kwa)
          self.manager = manager
  
  
      def enter(self):
          """"""
          if not self.manager.inited:
              self.manager.setup(**self.manager._inits)
  
  
      def exit(self):
          """"""
          pass
  