# -*- 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.

    Inherited Attributes:
        .name is LMDB database name did2offer
        .temp is Boolean, True means open db in /tmp directory
        .headDirPath is head directory path for db
        .mode is numeric os dir permissions for db directory
        .path is LMDB main (super) database directory path
        .env is LMDB main (super) database environment
        .opened is Boolean, True means LMDB .env at .path is opened.
                            Otherwise LMDB .env is closed

    Attributes:
        .gbls is named sub DB whose values are global parameters or 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 is 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 is 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 is 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 is 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 is 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:

    Directory Mode for Restricted Access Permissions
    stat.S_ISVTX  is Sticky bit. When this bit is set on a directory it means
        that a file in that directory can be renamed or deleted only by the
        owner of the file, by the owner of the directory, or by a privileged process.

    stat.S_IRUSR Owner has read permission.
    stat.S_IWUSR Owner has write permission.
    stat.S_IXUSR Owner has execute permission.
    """
    HeadDirPath = "/usr/local/var"  # default in /usr/local/var
    TailDirPath = "keri/keep"
    AltHeadDirPath = "~"  # put in ~ as fallback when desired not permitted
    AltTailDirPath = ".keri/keep"
    TempHeadDir = "/tmp"
    TempPrefix = "keri_keep_"
    TempSuffix = "_test"
    MaxNamedDBs = 8
    DirMode = stat.S_ISVTX | stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR  # 0o1700 == 960

    def __init__(self, headDirPath=None, dirMode=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
            dirMode is numeric optional os dir permissions mode
                default dirMode=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 dirMode is None:
            dirMode = self.DirMode  # defaults to restricted permissions for non temp

        super(Keeper, self).__init__(headDirPath=headDirPath, dirMode=dirMode,
                                     reopen=reopen, **kwa)

    def reopen(self, **kwa):
        """
        Open sub databases
        """
        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.MatterSuber(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.env


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
