Source code for keri.vdr.eventing

# -*- encoding: utf-8 -*-
"""
KERI
keri.vdr.eventing module

VC TEL  support
"""
import logging
from dataclasses import asdict
from ordered_set import OrderedSet as oset
from math import ceil
from  ordered_set import OrderedSet as oset

from hio.help import decking, ogler

from ..kering import (Kinds, Ilks, versify,
                    Version,  Vrsn_1_0,
                    MissingWitnessSignatureError, MissingAnchorError,
                    ValidationError, OutOfOrderError,
                    LikelyDuplicitousError, MissingEntryError, MissingAnchorError,
                    UntrustedKeyStateSource,UnverifiedReplyError,
                    OutOfOrderTxnStateError, MissingRegistryError)

from ..core import (SerderKERI, Salter, Prefixer, Verfer,
                    Number, Saider, Seqner,
                    Diger, Dater, SealEvent,
                    TraitDex, MtrDex, ample, verifySigs,
                    query as queryCore)

from ..db import (Baser, Broker, Komer, LMDBer,
                  Suber, OnSuber, CatCesrSuber, IoDupSuber,
                  CesrDupSuber, OnIoDupSuber, SerderSuber,
                  CesrIoSetSuber, CatCesrIoSetSuber, CesrSuber,
                  openLMDB, dgKey, snKey, dgKey, snKey)


from ..core import (Counter, Number, Diger, Dater,
                    Prefixer, Verfer, Cigar, Saider,
                    Seqner, SerderACDC, SerderKERI,
                    Siger, CtrDex_1_0, Codens)

from ..help import helping

from .vdring import RegistryRecord, RegStateRecord, VcStateRecord


logger = ogler.getLogger()



[docs] def incept( pre, toad=None, baks=None, nonce=None, cnfg=None, version=Version, kind=Kinds.json, code=MtrDex.Blake3_256, ): """ Returns serder of credential registry inception (vcp) message event Returns serder of vcp message event Utility function to create a Registry inception event Parameters: pre (str): issuer identifier prefix qb64 toad (Union(int,str)): int or str hex of backer threshold baks (list): the initial list of backers prefixes for VCs in the Registry nonce (str): qb64 encoded ed25519 random seed of credential registry cnfg (list): is list of strings TraitDex of configuration traits version (Versionage): the API version kind (str): the event type code (str): default code for Prefixer Returns: Serder: Event message Serder """ vs = versify(pvrsn=version, kind=kind, size=0) isn = 0 ilk = Ilks.vcp cnfg = cnfg if cnfg is not None else [] baks = baks if baks is not None else [] if TraitDex.NoBackers in cnfg and len(baks) > 0: raise ValueError("{} backers specified for NB vcp, 0 allowed".format(len(baks))) if len(oset(baks)) != len(baks): raise ValueError("Invalid baks = {}, has duplicates.".format(baks)) if isinstance(toad, str): toad = int(toad, 16) elif toad is None: if not baks: toad = 0 else: # compute default f and m for len(baks) toad = ample(len(baks)) if baks: if toad < 1 or toad > len(baks): # out of bounds toad raise ValueError("Invalid toad = {} for baks = {}".format(toad, baks)) else: if toad != 0: # invalid toad raise ValueError("Invalid toad = {} for baks = {}".format(toad, baks)) nonce = nonce if nonce is not None else Salter().qb64 ked = dict(v=vs, # version string t=ilk, d="", i="", # qb64 prefix ii=pre, s="{:x}".format(isn), # hex string no leading zeros lowercase c=cnfg, bt="{:x}".format(toad), # hex string no leading zeros lowercase b=baks, # list of qb64 may be empty n=nonce # nonce of random bytes to make each registry unique ) serder = SerderKERI(sad=ked, makify=True) return serder
[docs] def rotate( regk, dig, sn=1, toad=None, baks=None, cuts=None, adds=None, version=Version, kind=Kinds.json, ): """ Returns serder of registry rotation (brt) message event Returns serder of vrt message event Utility function to create a Registry rotation event Parameters: regk (str): identifier prefix qb64 dig (str): qb64 digest or prior event sn (int): sequence number toad (int): int or str hex of witness threshold baks (list): prior backers prefixes qb64 cuts (list): witness prefixes to cut qb64 adds (list): witness prefixes to add qb64 version (Versionage): the API version kind (str): the event type Returns: Serder: event message Serder """ if sn < 1: raise ValueError("Invalid sn = {} for vrt.".format(sn)) vs = versify(pvrsn=version, kind=kind, size=0) ilk = Ilks.vrt baks = baks if baks is not None else [] bakset = oset(baks) if len(bakset) != len(baks): raise ValueError("Invalid baks = {}, has duplicates.".format(baks)) cuts = cuts if cuts is not None else [] cutset = oset(cuts) if len(cutset) != len(cuts): raise ValueError("Invalid cuts = {}, has duplicates.".format(cuts)) if (bakset & cutset) != cutset: # some cuts not in wits raise ValueError("Invalid cuts = {}, not all members in baks.".format(cuts)) adds = adds if adds is not None else [] addset = oset(adds) if len(addset) != len(adds): raise ValueError("Invalid adds = {}, has duplicates.".format(adds)) if cutset & addset: # non empty intersection raise ValueError("Intersecting cuts = {} and adds = {}.".format(cuts, adds)) if bakset & addset: # non empty intersection raise ValueError("Intersecting baks = {} and adds = {}.".format(baks, adds)) newbakset = (bakset - cutset) | addset if len(newbakset) != (len(baks) - len(cuts) + len(adds)): # redundant? raise ValueError("Invalid member combination among baks = {}, cuts ={}, " "and adds = {}.".format(baks, cuts, adds)) if isinstance(toad, str): toad = int(toad, 16) elif toad is None: if not newbakset: toad = 0 else: # compute default f and m for len(newbakset) toad = ample(len(newbakset)) if newbakset: if toad < 1 or toad > len(newbakset): # out of bounds toad raise ValueError("Invalid toad = {} for resultant wits = {}" "".format(toad, list(newbakset))) else: if toad != 0: # invalid toad raise ValueError("Invalid toad = {} for resultant wits = {}" "".format(toad, list(newbakset))) ked = dict(v=vs, # version string t=ilk, d="", i=regk, # qb64 prefix p=dig, s="{:x}".format(sn), # hex string no leading zeros lowercase bt="{:x}".format(toad), # hex string no leading zeros lowercase br=cuts, # list of qb64 may be empty ba=adds, # list of qb64 may be empty ) serder = SerderKERI(sad=ked, makify=True) return serder
[docs] def issue( vcdig, regk, version=Version, kind=Kinds.json, dt=None ): """ Returns serder of issuance (iss) message event Returns serder of iss message event Utility function to create a VC issuance event Parameters: vcdig (str): qb64 SAID of credential regk (str): qb64 AID of credential registry version (Versionage): the API version kind (str): the event type dt (str): ISO 8601 formatted date string of issuance date Returns: Serder: event message Serder """ vs = versify(pvrsn=version, kind=kind, size=0) ked = dict(v=vs, # version string t=Ilks.iss, d="", i=vcdig, # qb64 prefix s="{:x}".format(0), # hex string no leading zeros lowercase ri=regk, dt=helping.nowIso8601() ) if dt is not None: ked["dt"] = dt serder = SerderKERI(sad=ked, makify=True) return serder
[docs] def revoke( vcdig, regk, dig, version=Version, kind=Kinds.json, dt=None ): """ Returns serder of backerless credential revocation (rev) message event Returns serder of rev message event Utility function to create a VC revocation vent Parameters: vcdig (str): qb64 SAID of credential regk (str): qb64 AID of credential registry dig (str): digest of previous event qb64 version (Versionage): the API version kind (str): the event type dt (str): ISO 8601 formatted date string of revocation date Returns: Serder: event message Serder """ vs = versify(pvrsn=version, kind=kind, size=0) isn = 1 ilk = Ilks.rev ked = dict(v=vs, t=ilk, d="", i=vcdig, s="{:x}".format(isn), # hex string no leading zeros lowercase ri=regk, p=dig, dt=helping.nowIso8601() ) if dt is not None: ked["dt"] = dt _, ked = Saider.saidify(sad=ked) serder = SerderKERI(sad=ked, makify=True) return serder
[docs] def backerIssue( vcdig, regk, regsn, regd, version=Version, kind=Kinds.json, dt=None, ): """ Returns serder of backer issuance (bis) message event Returns serder of bis message event Utility function to create a VC issuance event Parameters: vcdig (str): qb64 SAID of credential regk (str): qb64 AID of credential registry regsn (int): sequence number of anchoring registry TEL event regd (str): digest qb64 of anchoring registry TEL event version (Versionage): the API version kind (str): the event type dt (str): ISO 8601 formatted date string of issuance date Returns: Serder: event message Serder """ vs = versify(pvrsn=version, kind=kind, size=0) isn = 0 ilk = Ilks.bis seal = SealEvent(regk, "{:x}".format(regsn), regd) ked = dict(v=vs, # version string t=ilk, d="", i=vcdig, # qb64 prefix ii=regk, s="{:x}".format(isn), # hex string no leading zeros lowercase ra=seal._asdict(), dt=helping.nowIso8601(), ) _, ked = Saider.saidify(sad=ked) if dt is not None: ked["dt"] = dt serder = SerderKERI(sad=ked, makify=True) return serder
[docs] def backerRevoke( vcdig, regk, regsn, regd, dig, version=Version, kind=Kinds.json, dt=None ): """ Returns serder of backer credential revocation (brv) message event Returns serder of brv message event Utility function to create a VC revocation event Parameters: vcdig (str): qb64 SAID of credential regk (str): qb64 AID of credential registry regsn (int): sequence number of anchoring registry TEL event regd (str): digest qb64 of anchoring registry TEL event dig (str) digest of previous event qb64 version (Versionage): the API version kind (str): the event type dt (str): ISO 8601 formatted date string of issuance date Returns: Serder: event message Serder """ vs = versify(pvrsn=version, kind=kind, size=0) isn = 1 ilk = Ilks.brv seal = SealEvent(regk, "{:x}".format(regsn), regd) ked = dict(v=vs, t=ilk, d="", i=vcdig, s="{:x}".format(isn), # hex string no leading zeros lowercase p=dig, ra=seal._asdict(), dt=helping.nowIso8601(), ) _, ked = Saider.saidify(sad=ked) if dt is not None: ked["dt"] = dt serder = SerderKERI(sad=ked, makify=True) return serder
[docs] def state(pre, said, sn, ri, eilk, dts=None, # default current datetime toad=None, # default based on wits wits=None, # default to [] cnfg=None, # default to [] version=Version, ): """ Utility function to create a RegStateRecord of state notice of a given Registry Event Log (REL) Returns: rsr: (RegStateRecord): instance Parameters: pre (str): identifier prefix qb64 sn (int): int sequence number of latest event said (str): digest of latest event ri (str): qb64 AID of credential registry eilk (str): message type (ilk) oflatest event a (dict): key event anchored seal data dts (str) ISO 8601 formated current datetime toad (int): int of witness threshold wits (list): list of witness prefixes qb64 cnfg (list): list of strings TraitDex of configuration traits version (str): Version instance kind (str): serialization kind Returns: Serder: Event message Serder Key State Dict:: { "v": "KERI10JSON00011c_", "i": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", "s": "2":, "p": "EYAfSVPzhzZ-i0d8JZS6b5CMAoTNZH3ULvaU6JR2nmwy", "d": "EAoTNZH3ULvaU6JR2nmwyYAfSVPzhzZ-i0d8JZS6b5CM", "ri": "EYAfSVPzhzZ-i0d8JZS6b5CMAoTNZH3ULvaU6JR2nmwy", "dt": "2020-08-22T20:35:06.687702+00:00", "et": "vrt", "a": {i=12, d="EYAfSVPzhzS6b5CMaU6JR2nmwyZ-i0d8JZAoTNZH3ULv"}, "k": ["DaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM"], "n": "EZ-i0d8JZAoTNZH3ULvaU6JR2nmwyYAfSVPzhzS6b5CM", "bt": "1", "b": ["DnmwyYAfSVPzhzS6b5CMZ-i0d8JZAoTNZH3ULvaU6JR2"], "di": "EYAfSVPzhzS6b5CMaU6JR2nmwyZ-i0d8JZAoTNZH3ULv", "c": ["EO"], } """ #vs = versify(version=version, kind=kind, size=0) if sn < 0: raise ValueError("Negative sn = {} in key state.".format(sn)) if eilk not in (Ilks.vcp, Ilks.vrt): raise ValueError("Invalid evernt type et= in key state.".format(eilk)) if dts is None: dts = helping.nowIso8601() wits = wits if wits is not None else [] witset = oset(wits) if len(witset) != len(wits): raise ValueError("Invalid wits = {}, has duplicates.".format(wits)) if toad is None: if not witset: toad = 0 else: toad = max(1, ceil(len(witset) / 2)) if witset: if toad < 1 or toad > len(witset): # out of bounds toad raise ValueError("Invalid toad = {} for resultant wits = {}" "".format(toad, list(witset))) else: if toad != 0: # invalid toad raise ValueError("Invalid toad = {} for resultant wits = {}" "".format(toad, list(witset))) cnfg = cnfg if cnfg is not None else [] rsr = RegStateRecord( vn=list(version), # version number as list [major, minor] i=ri, # qb64 registry SAID s="{:x}".format(sn), # lowercase hex string no leading zeros d=said, ii=pre, dt=dts, et=eilk, bt="{:x}".format(toad), # hex string no leading zeros lowercase b=wits, # list of qb64 may be empty c=cnfg if cnfg is not None else [], ) return rsr # return RegStateRecord use asdict(rsr) to get dict version
[docs] def vcstate(vcpre, said, sn, ri, eilk, a, ra=None, dts=None, # default current datetime version=Version, kind=Kinds.json, ): """ Returns the credential transaction state notification Returns serder of credential transaction state notification message. Utility function to automate creation of tsn events. Parameters: vcpre (str): is qb64 SAID of the credential said (str): is qb64 digest of latest event sn (int): sequence number of latest event ri (str): registry identifier ra (dict): optional registry seal for registries with backers eilk (str): is message type (ilk) of latest event a (dict): is seal for anchor in KEL dts (str): iso8601 formatted date string of state version (Version): is KERI version instance kind (str): is serialization kind Credential Transaction State Dict:: { "v": "KERI10JSON00012d_", "i": "EDGhJ8V1tuwH55Bk0fBFe9L0za2BUNOt2FX4GUeOLNHQ", "s": "0", "d": "ENNTabgWbaNqOKLqEZdQCjxbafwwSoXNzAsE1Enq-kdk", "ri": "EoN_Ln_JpgqsIys-jDOH8oWdxgWqs7hzkDGeLWHb9vSY", "a": { "s": 3, "d": "Ex7i6wv4YzDRTO9_iHkTQSXrvLYldSd_UEjNfqia3Pqc" }, "dt": "2021-01-01T00:00:00.000000+00:00", "et": "bis" } """ if sn < 0: raise ValueError("Negative sn = {} in key state.".format(sn)) if eilk not in (Ilks.iss, Ilks.bis, Ilks.rev, Ilks.brv): raise ValueError("Invalid event type et= in key state.".format(eilk)) if dts is None: dts = helping.nowIso8601() if ra is None: ra = dict() vsr = VcStateRecord(vn=list(version), # version string i=vcpre, # qb64 prefix s="{:x}".format(sn), # lowercase hex string no leading zeros d=said, ri=ri, ra=ra, a=a, dt=dts, et=eilk, ) return vsr # return vc state record data class
[docs] def query(regk, vcid, route="", replyRoute="", dt=None, dta=None, dtb=None, stamp=None, version=Version, kind=Kinds.json ): """ Returns serder of credentialquery (qry) event message. Returns serder of query event message. Utility function to automate creation of interaction events. Parameters: regk (str): qb64 AID of credential registry vcid (str): qb64 SAID of credential route (str): namesapaced path, '/' delimited, that indicates data flow handler (behavior) to processs the query replyRoute (str): namesapaced path, '/' delimited, that indicates data flow handler (behavior) to processs reply message to query if any. dt (str): ISO 8601 formatted datetime query dta (str): ISO 8601 formatted datetime after query dtb (str): ISO 8601 formatted datetime before query stamp (str): ISO 8601 formatted current datetime of query message version (Versionage): the API version kind (str): the event type Returns: Serder: query event message Serder """ qry = dict(i=vcid, ri=regk) if dt is not None: qry["dt"] = dt if dta is not None: qry["dta"] = dt if dtb is not None: qry["dtb"] = dt return queryCore(route=route, replyRoute=replyRoute, query=qry, stamp=stamp, version=version, kind=kind)
[docs] class Tever: """ Tever is KERI/ACDC transaction event log verifier class Only supports current version VERSION Has the following public attributes and properties: Class Attributes: .NoRegistrarBackers is Boolean True means do not allow backers (default to witnesses of controlling KEL) False means allow backers (ignore witnesses of controlling KEL) Attributes: .db is reference to Baser instance that managers the LMDB database .reg is regerence to Registry instance that manages VC LMDB database .regk is fully qualified base64 identifier prefix of own Registry if any .local is Boolean True means only process msgs for own events if .regk False means only process msgs for not own events if .regk .version is version of current event state .prefixer is prefixer instance fParemtersor current event state .sn is sequence number int .serder is Serder instance of current event with .serder.diger for digest .toad is int threshold of accountable duplicity .baks is list of qualified qb64 aids for backers .cuts is list of qualified qb64 aids for backers cut from prev wits list .adds is list of qualified qb64 aids for backers added to prev wits list .noBackers is boolean trait True means do not allow backers """ NoRegistrarBackers = False
[docs] def __init__(self, cues=None, rsr=None, serder=None, seqner=None, saider=None, bigers=None, db=None, reger=None, noBackers=None, estOnly=None, regk=None, local=False): """ Create incepting tever and state from registry inception serder Create incepting tever and state from registry inception serder Parameters: serder (Serder): instance of registry inception event rsr (RegStateRecord): transaction state notice state message Serder seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event said from controlling KEL. bigers (list): list of Siger instances of indexed backer signatures of event. Index is offset into baks list of latest est event db (Baser): instance of baser lmdb database reger (Reger): instance of VC lmdb database noBackers (bool): True means do not allow backer configuration estOnly (bool): True means do not allow interaction events regk (str): identifier prefix of own or local registry. May not be the prefix of this Tever's event. Some restrictions if present local (bool): True means only process msgs for own controller's events if .regk. False means only process msgs for not own events if .regk Returns: Tever: instance representing credential Registry """ if not (rsr or serder): raise ValueError("Missing required arguments. Need state or serder") self.reger = reger if reger is not None else Reger() self.cues = cues if cues is not None else decking.Deck() self.db = db if db is not None else Baser(reopen=True) self.local = True if local else False if rsr: # preload from state self.reload(rsr) return self.version = serder.pvrsn self.regk = regk ilk = serder.ked["t"] if ilk not in [Ilks.vcp]: raise ValidationError("Expected ilk {} got {} for evt: {}".format(Ilks.vcp, ilk, serder)) self.ilk = ilk self.incept(serder=serder) self.config(serder=serder, noBackers=noBackers, estOnly=estOnly) bigers = self.valAnchorBigs(serder=serder, seqner=seqner, saider=saider, bigers=bigers, toad=self.toad, baks=self.baks) self.logEvent(pre=self.prefixer.qb64b, sn=0, serder=serder, seqner=seqner, saider=saider, bigers=bigers, baks=self.baks) self.regk = self.prefixer.qb64
[docs] def reload(self, rsr): """ Reload Tever attributes (aka its state) from state serder Reload Tever attributes (aka its state) from state serder Parameters: rsr (RegStateRecord): instance of key stat notice 'ksn' message body """ ked = asdict(rsr) self.version = rsr.vn self.pre = ked["ii"] self.regk = ked["i"] self.prefixer = Prefixer(qb64=self.regk) self.sn = int(ked['s'], 16) self.ilk = ked["et"] self.toad = int(ked["bt"], 16) self.baks = ked["b"] self.noBackers = True if TraitDex.NoBackers in ked["c"] else False self.estOnly = True if TraitDex.EstOnly in ked["c"] else False if (raw := self.reger.tvts.get(keys=(self.prefixer.qb64, ked['d']))) is None: raise MissingEntryError("Corresponding event for state={} not found." "".format(ked)) self.serder = SerderKERI(raw=raw.encode("utf-8"))
[docs] def state(self): #state(self, kind=Serials.json) """ Returns RegStateRecord of state notice of given Registry Event Log (REL) Returns: rsr: (RegStateRecord): instance for this Tever """ cnfg = [] if self.noBackers: cnfg.append(TraitDex.NoBackers) dgkey = dgKey(self.regk, self.serder.said) couple = self.reger.ancs.get(keys=dgkey) if couple is None: raise MissingEntryError(f"Missing anchor couple at key={dgkey!r}.") return (state(pre=self.pre, said=self.serder.said, sn=self.sn, ri=self.regk, dts=None, eilk=self.ilk, #a=dict(s=seqner.sn, d=diger.qb64), toad=self.toad, wits=self.baks, cnfg=cnfg, #kind=kind ) )
[docs] def incept(self, serder): """ Validate registry inception event and initialize local attributes Parse and validate registry inception event for this Tever. Update all local attributes with initial values. Parameters: serder (Serder): registry inception event (vcp) """ ked = serder.ked self.pre = ked["ii"] # which is not the AID of the serder in ked["i"] self.prefixer = Prefixer(qb64=serder.pre) # this not related to self.pre #if not self.prefixer.verify(ked=ked, prefixed=True): # invalid prefix #raise ValidationError("Invalid prefix = {} for registry inception evt = {}." #.format(self.prefixer.qb64, ked)) self.sn = Number(numh=ked["s"]).validate(inceptive=True).sn self.cuts = [] # always empty at inception since no prev event self.adds = [] # always empty at inception since no prev event baks = ked["b"] if len(oset(baks)) != len(baks): raise ValidationError("Invalid baks = {}, has duplicates for evt = {}." "".format(baks, ked)) self.baks = baks toad = int(ked["bt"], 16) if baks: if toad < 1 or toad > len(baks): # out of bounds toad raise ValidationError("Invalid toad = {} for baks = {} for evt = {}." "".format(toad, baks, ked)) else: if toad != 0: # invalid toad raise ValidationError("Invalid toad = {} for baks = {} for evt = {}." "".format(toad, baks, ked)) self.toad = toad self.serder = serder
[docs] def config(self, serder, noBackers=None, estOnly=None): """ Process cnfg field for configuration traits Parse and validate the configuration options for registry inception from the `c` field of the provided inception event. Parameters: serder (Serder): credential registry inception event `vcp` noBackers (bool): override flag for specifying a registry with no additional backers beyond the controlling KEL's witnesses """ # assign traits self.noBackers = (True if (noBackers if noBackers is not None else self.NoRegistrarBackers) else False) # ensure default noBackers is boolean self.estOnly = (True if (estOnly if estOnly is not None else False) else False) # ensure default estOnly is boolean cnfg = serder.ked["c"] # process cnfg for traits if TraitDex.NoBackers in cnfg: self.noBackers = True if TraitDex.EstOnly in cnfg: self.estOnly = True
[docs] def update(self, serder, seqner=None, saider=None, bigers=None): """ Process registry non-inception events. Process non-inception registry and credential events and update local Tever state for registry or credential Parameters: serder (Serder): instance of issuance or backer issuance event seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event SAID from controlling KEL. bigers (list): of Siger instances of indexed witness signatures. Index is offset into wits list of associated witness nontrans pre from which public key may be derived. """ ked = serder.ked ilk = ked["t"] #sn = ked["s"] icp = ilk in (Ilks.iss, Ilks.bis) # validate SN for #sn = validateSN(sn, inceptive=icp) sn = Number(numh=ked["s"]).validate(inceptive=icp).sn if ilk in (Ilks.vrt,): if self.noBackers is True: raise ValidationError("invalid rotation evt {} against backerless registry {}". format(ked, self.regk)) toad, baks, cuts, adds = self.rotate(serder, sn=sn) bigers = self.valAnchorBigs(serder=serder, seqner=seqner, saider=saider, bigers=bigers, toad=toad, baks=baks) self.sn = sn self.serder = serder self.ilk = ilk self.toad = toad self.baks = baks self.cuts = cuts self.adds = adds self.logEvent(pre=self.prefixer.qb64b, sn=sn, serder=serder, seqner=seqner, saider=saider, bigers=bigers, baks=self.baks) return elif ilk in (Ilks.iss, Ilks.bis): self.issue(serder, seqner=seqner, saider=saider, sn=sn, bigers=bigers) elif ilk in (Ilks.rev, Ilks.brv): self.revoke(serder, seqner=seqner, saider=saider, sn=sn, bigers=bigers) else: # unsupported event ilk so discard raise ValidationError("Unsupported ilk = {} for evt = {}.".format(ilk, ked))
[docs] def rotate(self, serder, sn): """ Process registry management TEL, non-inception events (vrt) Parameters: serder (Serder): registry rotation event sn (int): sequence number of event Returns: int: calculated backer threshold list: new list of backers after applying cuts and adds to previous list list: list of backer adds processed from event list: list of backer cuts processed from event """ ked = serder.ked ilk = ked["t"] dig = ked["p"] #labels = VRT_LABELS # assumes ilk == Ilks.vrt #for k in labels: #if k not in ked: #raise ValidationError("Missing element = {} from {} event for " #"evt = {}.".format(k, ilk, ked)) if serder.pre != self.prefixer.qb64: raise ValidationError("Mismatch event aid prefix = {} expecting" " = {} for evt = {}.".format(ked["i"], self.prefixer.qb64, ked)) if not sn == (self.sn + 1): # sn not in order raise ValidationError("Invalid sn = {} expecting = {} for evt " "= {}.".format(sn, self.sn + 1, ked)) if not self.serder.compare(said=dig): # prior event dig not match raise ValidationError("Mismatch event dig = {} with state dig" " = {} for evt = {}.".format(ked["p"], self.serder.said, ked)) witset = oset(self.baks) cuts = ked["br"] cutset = oset(cuts) if len(cutset) != len(cuts): raise ValidationError("Invalid cuts = {}, has duplicates for evt = " "{}.".format(cuts, ked)) if (witset & cutset) != cutset: # some cuts not in baks raise ValidationError("Invalid cuts = {}, not all members in baks" " for evt = {}.".format(cuts, ked)) adds = ked["ba"] addset = oset(adds) if len(addset) != len(adds): raise ValidationError("Invalid adds = {}, has duplicates for evt = " "{}.".format(adds, ked)) if cutset & addset: # non empty intersection raise ValidationError("Intersecting cuts = {} and adds = {} for " "evt = {}.".format(cuts, adds, ked)) if witset & addset: # non empty intersection raise ValidationError("Intersecting baks = {} and adds = {} for " "evt = {}.".format(self.baks, adds, ked)) baks = list((witset - cutset) | addset) if len(baks) != (len(self.baks) - len(cuts) + len(adds)): # redundant? raise ValidationError("Invalid member combination among baks = {}, cuts ={}, " "and adds = {} for evt = {}.".format(self.baks, cuts, adds, ked)) toad = int(ked["bt"], 16) if baks: if toad < 1 or toad > len(baks): # out of bounds toad raise ValidationError("Invalid toad = {} for baks = {} for evt " "= {}.".format(toad, baks, ked)) else: if toad != 0: # invalid toad raise ValidationError("Invalid toad = {} for baks = {} for evt " "= {}.".format(toad, baks, ked)) return toad, baks, cuts, adds
[docs] def issue(self, serder, seqner, saider, sn, bigers=None): """ Process VC TEL issuance events (iss, bis) Validate and process credential issuance events. If valid, event is persisted in local datastore for TEL. Will escrow event if missing anchor or backer signatures Parameters: serder (Serder): instance of issuance or backer issuance event seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event SAID from controlling KEL. sn (int): event sequence event bigers (list): of Siger instances of indexed witness signatures. Index is offset into wits list of associated witness nontrans pre from which public key may be derived. """ ked = serder.ked vcpre = ked["i"] ilk = ked["t"] vci = vcpre #labels = ISS_LABELS if ilk == Ilks.iss else BIS_LABELS #for k in labels: #if k not in ked: #raise ValidationError("Missing element = {} from {} event for " #"evt = {}.".format(k, ilk, ked)) if ilk == Ilks.iss: # simple issue if self.noBackers is False: raise ValidationError("invalid simple issue evt {} against backer based registry {}". format(ked, self.regk)) regi = ked["ri"] if regi != self.prefixer.qb64: raise ValidationError("Mismatch event regi prefix = {} expecting" " = {} for evt = {}.".format(regi, self.prefixer.qb64, ked)) # check if fully anchored if not self.verifyAnchor(serder=serder, seqner=seqner, saider=saider): self.escrowALEvent(serder=serder, seqner=seqner, saider=saider) raise MissingAnchorError("Failure verify event = {} " "".format(serder.ked, )) self.logEvent(pre=vci, sn=sn, serder=serder, seqner=seqner, saider=saider) elif ilk == Ilks.bis: # backer issue if self.noBackers is True: raise ValidationError("invalid backer issue evt {} against backerless registry {}". format(ked, self.regk)) rtoad, baks = self.getBackerState(ked) bigers = self.valAnchorBigs(serder=serder, seqner=seqner, saider=saider, bigers=bigers, toad=rtoad, baks=baks) self.logEvent(pre=vci, sn=sn, serder=serder, seqner=seqner, saider=saider, bigers=bigers) else: raise ValidationError("Unsupported ilk = {} for evt = {}.".format(ilk, ked))
[docs] def revoke(self, serder, seqner, saider, sn, bigers=None): """ Process VC TEL revocation events (rev, brv) Validate and process credential revocation events. If valid, event is persisted in local datastore for TEL. Will escrow event if missing anchor or backer signatures Parameters: serder (Serder): instance of issuance or backer issuance event seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event digest from controlling KEL. sn (int): event sequence event bigers (list): of Siger instances of indexed witness signatures. Index is offset into wits list of associated witness nontrans pre from which public key may be derived. """ ked = serder.ked vcpre = ked["i"] ilk = ked["t"] #labels = REV_LABELS if ilk == Ilks.rev else BRV_LABELS #for k in labels: #if k not in ked: #raise ValidationError("Missing element = {} from {} event for " #"evt = {}.".format(k, ilk, ked)) # have to compare with VC issuance serder vci = vcpre dig = self.reger.tels.get(keys=vci, on=sn - 1) ievt = self.reger.tvts.get(keys=(vci, dig)) if ievt is None: raise ValidationError("revoke without issue... probably have to escrow") iserder = SerderKERI(raw=ievt.encode("utf-8")) if not iserder.compare(said=ked["p"]): # prior event dig not match raise ValidationError("Mismatch event dig = {} with state dig" " = {} for evt = {}.".format(ked["p"], self.serder.said, ked)) if ilk in (Ilks.rev,): # simple revoke if self.noBackers is False: raise ValidationError("invalid simple issue evt {} against backer based registry {}". format(ked, self.regk)) # check if fully anchored if not self.verifyAnchor(serder=serder, seqner=seqner, saider=saider): self.escrowALEvent(serder=serder, seqner=seqner, saider=saider) raise MissingAnchorError("Failure verify event = {} " "".format(serder.ked)) self.logEvent(pre=vci, sn=sn, serder=serder, seqner=seqner, saider=saider) self.cues.push(dict(kin="revoked", serder=serder)) elif ilk in (Ilks.brv,): # backer revoke if self.noBackers is True: raise ValidationError("invalid backer issue evt {} against backerless registry {}". format(ked, self.regk)) rtoad, baks = self.getBackerState(ked) bigers = self.valAnchorBigs(serder=serder, seqner=seqner, saider=saider, bigers=bigers, toad=rtoad, baks=baks) self.logEvent(pre=vci, sn=sn, serder=serder, seqner=seqner, saider=saider, bigers=bigers) self.cues.push(dict(kin="revoked", serder=serder)) else: raise ValidationError("Unsupported ilk = {} for evt = {}.".format(ilk, ked))
[docs] def vcState(self, vci): """ Calculate state (issued/revoked) of VC from db. Returns None if never issued from this Registry Parameters: vci (str): qb64 VC identifier Returns: status (Serder): transaction event state notification message """ digs = [] for _, _, dig in self.reger.tels.getAllItemIter(keys=vci.encode("utf-8")): digs.append(dig) if len(digs) == 0: return None vcsn = len(digs) - 1 vcdig = digs[-1].encode("utf-8") dgkey = dgKey(vci, vcdig) # get message raw = self.reger.tvts.get(keys=dgkey) serder = SerderKERI(raw=raw.encode("utf-8")) if self.noBackers: vcilk = Ilks.iss if len(digs) == 1 else Ilks.rev ra = dict() else: vcilk = Ilks.bis if len(digs) == 1 else Ilks.brv ra = serder.ked["ra"] dgkey = dgKey(vci, vcdig) couple = self.reger.ancs.get(keys=dgkey) if couple is None: raise MissingEntryError(f"Missing anchor couple at key={dgkey!r}.") number, diger = couple seqner = Seqner(sn=number.num) saider = Saider(qb64=diger.qb64) return vcstate(vcpre=vci, said=vcdig.decode("utf-8"), sn=vcsn, ri=self.prefixer.qb64, dts=serder.ked['dt'], eilk=vcilk, ra=ra, a=dict(s=seqner.sn, d=saider.qb64), )
[docs] def vcSn(self, vci): """ Calculates the current seq no of VC from db. Returns None if never issued from this Registry Parameters: vci (str): qb64 VC identifier Returns: int: current TEL sequence number of credential or None if not found """ cnt = self.reger.tels.cntAll(keys=vci) return None if cnt == 0 else cnt - 1
[docs] def logEvent(self, pre, sn, serder, seqner, saider, bigers=None, baks=None): """ Update associated logs for verified event. Update is idempotent. Logs will not write dup at key if already exists. Parameters: pre (str): is event prefix sn (int): is event sequence number serder (Serder): is Serder instance of current event seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event SAID from controlling KEL. bigers (list): is optional list of Siger instance of indexed backer sigs baks (list): is optional list of qb64 non-trans identifiers of backers """ if hasattr(pre, "encode"): pre = pre.encode("utf-8") # convert str to bytes dig = serder.saidb key = dgKey(pre, dig) number = Number(num=seqner.sn) diger = Diger(qb64=saider.qb64) self.reger.ancs.put(keys=key, val=(number, diger)) if bigers: self.reger.tibs.pin(keys=key, vals=bigers) if baks: self.reger.baks.rem(key) self.reger.baks.put(key, [bak.encode("utf-8") for bak in baks]) self.reger.tets.pin(keys=(pre.decode("utf-8"), dig.decode("utf-8")), val=Dater()) self.reger.tvts.put(keys=key, val=serder.raw) self.reger.tels.put(keys=pre, on=sn, val=dig) logger.info("Tever: Added to TEL valid %s event %s said=%s reg=%.8s iss=%.8s", serder.ilk, pre.decode(), serder.said, self.regk, self.pre) logger.debug("TEL Event Body=\n%s\n", serder.pretty())
[docs] def valAnchorBigs(self, serder, seqner, saider, bigers, toad, baks): """ Validate anchor and backer signatures (bigers) when provided. Validates sigers signatures by validating indexes, verifying signatures, and validating threshold sith. Validate backer receipts by validating indexes, verifying backer signatures and validating toad. Backer validation is a function of .regk and .local Parameters: serder (Serder): instance of event seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event said from controlling KEL. bigers (list) Siger instances of indexed witness signatures. Index is offset into wits list of associated witness nontrans pre from which public key may be derived. toad (int): str hex of witness threshold baks (list): qb64 non-transferable prefixes of backers used to derive werfers for bigers Returns: list: unique validated signature verified members of inputed bigers """ for bak in baks: print("BAK :", bak) berfers = [Verfer(qb64=bak) for bak in baks] # get unique verified bigers and bindices lists from bigers list bigers, bindices = verifySigs(raw=serder.raw, sigers=bigers, verfers=berfers) # each biger now has werfer of corresponding wit # check if fully anchored if not self.verifyAnchor(serder=serder, seqner=seqner, saider=saider): self.escrowALEvent(serder=serder, seqner=seqner, saider=saider, bigers=bigers, baks=baks) raise MissingAnchorError("Failure verify event = {}".format(serder.ked)) # Kevery .process event logic prevents this from seeing event when # not local and event pre is own pre if ((baks and not self.regk) or # in promiscuous mode so assume must verify toad (baks and not self.local and self.regk and self.regk not in baks)): # validate that event is fully witnessed if isinstance(toad, str): toad = int(toad, 16) if toad < 0 or len(baks) < toad: raise ValidationError("Invalid toad = {} for wits = {} for evt" " = {}.".format(toad, baks, serder.ked)) if len(bindices) < toad: # not fully witnessed yet self.escrowPWEvent(serder=serder, seqner=seqner, saider=saider, bigers=bigers) msg = (f"Failure satisfying toad = {toad} on witness sigs " f"for {[siger.qb64 for siger in bigers]} for evt = {serder.said}") logger.info(msg) logger.debug(f"Event Body=\n%s\n", serder.pretty()) raise MissingWitnessSignatureError(msg) return bigers
[docs] def verifyAnchor(self, serder, seqner=None, saider=None): """ Retrieve specified anchoring event and verify seal Retrieve event from db using anchor, get seal from event eserder and verify pre, sn and dig against serder Parameters: serder (Serder): anchored TEL event seqner (Seqner): sequence number of anchoring event saider (Saider): digest of anchoring event Returns: bool: True is anchoring event exists in database and seal is valid against TEL event. """ if seqner is None or saider is None: return False dig = self.db.kels.getLast(keys=self.pre, on=seqner.sn) if not dig: return False else: dig = dig.encode("utf-8") # retrieve event by dig if not (eserder := self.db.evts.get(keys=(self.pre, dig))): return False if eserder.said != saider.qb64: return False seal = eserder.ked["a"] if seal is None or len(seal) != 1: return False seal = seal[0] spre = seal["i"] ssn = seal["s"] sdig = seal["d"] if spre == serder.ked["i"] and ssn == serder.ked["s"] \ and serder.said == sdig: return True return False
[docs] def escrowPWEvent(self, serder, seqner, saider, bigers=None): """ Update associated logs for escrow of partially witnessed event Parameters: serder (Serder): instance of event seqner (Seqner): sequence number for anchor seal saider (Saider): digest of anchor bigers (list): Siger instance of indexed witness sigs """ dgkey = dgKey(serder.preb, serder.saidb) number = Number(num=seqner.sn) diger = Diger(qb64=saider.qb64) self.reger.ancs.put(keys=dgkey, val=(number, diger)) if bigers: self.reger.tibs.pin(keys=dgkey, vals=bigers) self.reger.tvts.put(keys=dgkey, val=serder.raw) self.reger.twes.put(keys=serder.preb, on=serder.sn, vals=serder.saidb) logger.debug("Tever state: Escrowed partially witnessed " "event = %s", serder.ked)
[docs] def escrowALEvent(self, serder, seqner, saider, bigers=None, baks=None): """ Update associated logs for escrow of anchorless event Parameters: serder (Serder): instance of event seqner (Seqner): sequence number for anchor seal saider (Saider): SAID of anchor bigers (list): Siger instance of indexed witness sigs baks (list): qb64 of new backers Returns: bool: True if escrow is successful, False otherwith (eg. already escrowed) """ key = dgKey(serder.preb, serder.saidb) if seqner and saider: number = Number(num=seqner.sn) diger = Diger(qb64=saider.qb64) self.reger.ancs.put(keys=key, val=(number, diger)) if bigers: self.reger.tibs.pin(keys=key, vals=bigers) if baks: self.reger.baks.rem(key) self.reger.baks.put(key, [bak.encode("utf-8") for bak in baks]) self.reger.tvts.put(keys=key, val=serder.raw) logger.debug("Tever state: Escrowed anchorless event " "event = %s", serder.ked) return self.reger.taes.put(keys=serder.preb, on=serder.sn, vals=serder.saidb)
[docs] def getBackerState(self, ked): """ Calculate and return the current list of backers for event dict Parameters: ked (dict): event dict Returns: list: qb64 of current list of backers for state at ked """ rega = ked["ra"] regi = rega["i"] regd = rega["d"] if regi != self.prefixer.qb64: raise ValidationError("Mismatch event regk prefix = {} expecting" " = {} for evt = {}.".format(self.regk, self.prefixer.qb64, ked)) # load backer list and toad (via event) for specific event in registry from seal in event dgkey = dgKey(regi, regd) revt = self.reger.tvts.get(keys=dgkey) if revt is None: raise ValidationError("have to escrow this somewhere") rserder = SerderKERI(raw=revt.encode("utf-8")) # the backer threshold at this event in mgmt TEL rtoad = rserder.ked["bt"] baks = [bak for bak in self.reger.baks.get(dgkey)] return rtoad, baks
[docs] class Tevery: """ Tevery (Transaction Event Message Processing Facility) Tevery processes an incoming message stream composed of KERI key event related messages and attachments. Tevery acts as a Tever (transaction event verifier) factory for managing transaction state of KERI credential registries and associated credentials. Attributes: db (Baser): local LMDB identifier database reger (Reger): local LMDB credential database local (bool): True means only process msgs for own events if .regk False means only process msgs for not own events if .regk cues (Deck): notices generated from processing events """ TimeoutTSN = 3600
[docs] def __init__(self, reger=None, db=None, local=False, lax=False, cues=None, rvy=None): """ Initialize instance: Parameters: reger (Reger): local LMDB credential database db (Baser): local LMDB identifier database local (bool): True means only process msgs for own events if .regk False means only process msgs for not own events if .regk cues (Deck): notices generated from processing events """ self.db = db if db is not None else Baser(reopen=True) # default name = "main" self.rvy = rvy self.reger = reger if reger is not None else Reger() self.local = True if local else False # local vs nonlocal restrictions self.lax = True if lax else False self.cues = cues if cues is not None else decking.Deck()
@property def tevers(self): """ Returns .reger.tevers read through cache of credential registries """ return self.reger.tevers @property def kevers(self): """ Returns .db.kevers read through cache of key event logs """ return self.db.kevers @property def registries(self): """ Returns .reger.registries """ return self.reger.registries
[docs] def processEvent(self, serder, seqner=None, saider=None, wigers=None, **kwa): """ Process one event serder with attached indexed signatures sigers Validates event against current state of registry or credential, creating registry on inception events and processing change in state to credential or registry for other events Parameters: serder (Serder): event to process seqner (Seqner): issuing event sequence number from controlling KEL. saider (Saider): issuing event digest from controlling KEL. wigers (list): optional list of Siger instances of attached witness indexed sigs """ ked = serder.ked try: # see if code of pre is supported and matches size of pre Prefixer(qb64b=serder.preb) except Exception: # if unsupported code or bad size raises error raise ValidationError("Invalid pre = {} for evt = {}." "".format(serder.pre, ked)) regk = self.registryKey(serder) pre = serder.pre ked = serder.ked ilk = ked["t"] # validate SN inceptive = ilk in (Ilks.vcp, Ilks.iss, Ilks.bis) sn = Number(numh=ked["s"]).validate(inceptive=inceptive).sn if not self.lax: if self.local: if regk not in self.registries: # nonlocal event when in local mode raise ValueError("Nonlocal event regk={} when local mode for registries={}." "".format(regk, self.registries)) else: if regk in self.registries: # local event when not in local mode raise ValueError("Local event regk={} when nonlocal mode." "".format(regk)) if regk not in self.tevers: # first seen for this registry if ilk in [Ilks.vcp]: # incepting a new registry, Tever create will validate anchor, etc. tever = Tever(serder=serder, seqner=seqner, saider=saider, bigers=wigers, reger=self.reger, db=self.db, regk=regk, local=self.local, cues=self.cues) self.tevers[regk] = tever if regk not in self.registries: # witness style backers will need to send receipts so lets queue them up for now # actually, lets not because the Kevery has no idea what to do with them! # self.cues.append(dict(kin="receipt", serder=serder)) pass else: # out of order, need to escrow self.escrowOOEvent(serder=serder, seqner=seqner, saider=saider) msg = f"Escrowed out of order event of type = {ilk} pre = {pre} SAID = {serder.said}" logger.debug(msg) logger.debug("TEL Event Body=\n%s\n", serder.pretty()) raise OutOfOrderError(msg) else: if ilk in (Ilks.vcp,): # we don't have multiple signatures to verify so this # is already first seen and then likely duplicitious msg = f"Likely Duplicitous Event of type={serder.ilk} sn={sn} SAID={serder.said}" logger.debug(msg) logger.debug("TEL Event Body=\n%s\n", serder.pretty()) raise LikelyDuplicitousError(msg) tever = self.tevers[regk] tever.cues = self.cues if ilk in [Ilks.vrt]: sno = tever.sn + 1 # proper sn of new inorder event else: esn = tever.vcSn(pre) sno = 0 if esn is None else esn + 1 #if not serder.saider.verify(sad=serder.sad): #raise ValidationError("Invalid SAID {} for event {}".format(said, serder.ked)) if sn > sno: # sn later than sno so out of order escrow # escrow out-of-order event self.escrowOOEvent(serder=serder, seqner=seqner, saider=saider) raise OutOfOrderError("Out-of-order event={}.".format(ked)) elif sn == sno: # new inorder event tever.update(serder=serder, seqner=seqner, saider=saider, bigers=wigers) if regk not in self.registries: # witness style backers will need to send receipts so lets queue them up for now # actually, lets not because the Kevery has no idea what to do with them! # self.cues.append(dict(kin="receipt", serder=serder)) pass else: # duplicitious msg = f"Likely Duplicitous Event type={serder.ilk} sn={sn} SAID={serder.said}" logger.debug(msg) logger.debug("TEL Event Body=\n%s\n", serder.pretty()) raise LikelyDuplicitousError(msg)
[docs] def processQuery(self, serder, source=None, sigers=None, cigars=None, **kwa): """ Process TEL query event message (qry) Process query mode replay message for collective or single element query. Will cue response message with kin of "replay". Assume promiscuous mode for now. Parameters: serder (Serder): is query message serder source (qb64): identifier prefix of querier sigers (list): Siger instances of attached controller indexed sigs cigars (list): Siger instances of non-transferable signatures ToDo: Need to verify sigers or cigars on query """ ked = serder.ked ilk = ked["t"] route = ked["r"] qry = ked["q"] # do signature validation and replay attack prevention logic here # src, dt, route if source is None and cigars: source = cigars[0].verfer if route == "tels": mgmt = qry["ri"] src = qry["src"] cloner = self.reger.clonePreIter(pre=mgmt, fn=0) # create iterator at 0 msgs = list() # outgoing messages for msg in cloner: msgs.append(msg) if vci := qry["i"]: cloner = self.reger.clonePreIter(pre=vci, fn=0) # create iterator at 0 for msg in cloner: msgs.append(msg) if msgs: self.cues.append(dict(kin="replay", src=src, dest=source.qb64, msgs=msgs)) elif route == "tsn": ri = qry["ri"] if ri in self.tevers: tever = self.tevers[ri] tsn = tever.state() self.cues.push(dict(kin="reply", route="/tsn/registry", data=asdict(tsn), dest=source)) if vcpre := qry["i"]: tsn = tever.vcState(vcpre=vcpre) self.cues.push(dict(kin="reply", route="/tsn/credential", data=asdict(tsn), dest=source)) else: raise ValidationError("invalid query message {} for evt = {}".format(ilk, ked))
[docs] def registerReplyRoutes(self, router): """ Register the routes for processing messages embedded in `rpy` event messages Parameters: router(Router): reply message router """ router.addRoute("/tsn/registry/{aid}", self, suffix="RegistryTxnState") router.addRoute("/tsn/credential/{aid}", self, suffix="CredentialTxnState")
[docs] def processReplyRegistryTxnState(self, *, serder, diger, route, cigars=None, tsgs=None, **kwargs): """ Process one reply message for key state = /tsn/registry Process one reply message for key state = /tsn/registry with either attached nontrans receipt couples in cigars or attached trans indexed sig groups in tsgs. Assumes already validated saider, dater, and route from serder.ked Parameters: serder (Serder): instance of reply msg (SAD) saider (Diger): instance from said in serder (SAD) route (str): reply route cigars (list): of Cigar instances that contain nontrans signing couple signature in .raw and public key in .verfer tsgs (list): tuples (quadruples) of form (prefixer, seqner, diger, [sigers]) where: prefixer is pre of trans endorser seqner is sequence number of trans endorser's est evt for keys for sigs diger is digest of trans endorser's est evt for keys for sigs [sigers] is list of indexed sigs from trans endorser's keys from est evt Reply Message:: { "v" : "KERI10JSON00011c_", "t" : "rpy", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "/tsn/EgHOJJ9mgNosU2hgt7bsM8AViwgz--ey3ZXWgfIcxdpI", "a" : { "v": "KERI10JSON0001b0_", "i": "EoN_Ln_JpgqsIys-jDOH8oWdxgWqs7hzkDGeLWHb9vSY", "s": "1", "d": "EpltHxeKueSR1a7e0_oSAhgO6U7VDnX7x4KqNCwBqbI0", "ii": "EaKJ0FoLxO1TYmyuprguKO7kJ7Hbn0m0Wuk5aMtSrMtY", "dt": "2021-01-01T00:00:00.000000+00:00", "et": "vrt", "a": { "s": 2, "d": "Ef12IRHtb_gVo5ClaHHNV90b43adA0f8vRs3jeU-AstY" }, "bt": "1", "br": [], "ba": [ "BwFbQvUaS4EirvZVPUav7R_KDHB8AKmSfXNpWnZU_YEU" ], "b": [ "BwFbQvUaS4EirvZVPUav7R_KDHB8AKmSfXNpWnZU_YEU" ], "c": [] } } """ cigars = cigars if cigars is not None else [] tsgs = tsgs if tsgs is not None else [] # reply specific logic if not route.startswith("/tsn"): raise ValidationError(f"Usupported route={route} in {Ilks.rpy} " f"msg={serder.ked}.") aid = kwargs["aid"] data = serder.ked["a"] dater = Dater(dts=serder.ked["dt"]) rsr = RegStateRecord(**data) # fetch from serder to process regk = rsr.i pre = rsr.ii sn = int(rsr.s, 16) if pre not in self.kevers: if self.reger.txnsb.escrowStateNotice(typ="registry-mae", pre=regk, aid=aid, serder=serder, diger=diger, dater=dater, cigars=cigars, tsgs=tsgs): self.cues.append(dict(kin="query", q=dict(pre=pre))) raise MissingAnchorError("Failure verify event = {} ".format(serder.ked)) # Load backers from either tsn or Kever of issuer cnfg = rsr.c if TraitDex.NoBackers in cnfg: kevers = self.kevers[pre] baks = kevers.wits else: baks = rsr.b wats = set() for _, habr in self.db.habs.getTopItemIter(): wats |= set(habr.watchers) # not in promiscuous mode if not self.lax: # check source and ensure we should accept it if aid != pre and \ aid not in baks and \ aid not in wats: raise UntrustedKeyStateSource("transaction state notice for {} from untrusted source {} " .format(rsr.i, aid)) if regk in self.tevers: tever = self.tevers[regk] if int(rsr.s, 16) < tever.sn: raise ValidationError("Skipped stale transaction state at sn {} for {}." "".format(rsr.s, rsr.i)) keys = (regk, aid,) osaider = self.reger.txnsb.current(keys=keys) # get old said if any # BADA Logic accepted = self.rvy.acceptReply(serder=serder, saider=diger, route=route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: raise UnverifiedReplyError(f"Unverified registry txn state reply.") ldig = self.reger.tels.get(keys=regk, on=sn) # retrieve dig of last event at sn. # Only accept key state if for last seen version of event at sn if ldig is None: # escrow because event does not yet exist in database if self.reger.txnsb.escrowStateNotice(typ="registry-ooo", pre=regk, aid=aid, serder=serder, diger=diger, dater=dater, cigars=cigars, tsgs=tsgs): self.cues.append(dict(kin="telquery", q=dict(ri=regk))) raise OutOfOrderTxnStateError("Out of order txn state={}.".format(rsr)) tdiger = Diger(qb64=rsr.d) ldig = ldig.encode("utf-8") # retrieve last event itself of signer given sdig sraw = self.reger.tvts.get(keys=(regk, ldig)) # assumes db ensures that sraw must not be none because sdig was in KE sserder = SerderKERI(raw=sraw.encode("utf-8")) if sserder.said != tdiger.qb64: # mismatch events problem with replay raise ValidationError("Mismatch keystate at sn = {} with db." "".format(rsr.s)) self.reger.txnsb.updateReply(aid=aid, serder=serder, diger=tdiger, dater=dater) self.cues.append(dict(kin="txnStateSaved", record=rsr))
[docs] def processReplyCredentialTxnState(self, *, serder, diger, route, cigars=None, tsgs=None, **kwargs): """ Process one reply message for key state = /tsn/registry Process one reply message for key state = /tsn/registry with either attached nontrans receipt couples in cigars or attached trans indexed sig groups in tsgs. Assumes already validated saider, dater, and route from serder.ked Parameters: serder (Serder): instance of reply msg (SAD) saider (Diger): instance from said in serder (SAD) route (str): reply route cigars (list): of Cigar instances that contain nontrans signing couple signature in .raw and public key in .verfer tsgs (list): tuples (quadruples) of form (prefixer, seqner, diger, [sigers]) where: prefixer is pre of trans endorser seqner is sequence number of trans endorser's est evt for keys for sigs diger is digest of trans endorser's est evt for keys for sigs [sigers] is list of indexed sigs from trans endorser's keys from est evt Reply Message:: { "v" : "KERI10JSON00011c_", "t" : "rpy", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "/tsn/EgHOJJ9mgNosU2hgt7bsM8AViwgz--ey3ZXWgfIcxdpI", "a" : { "v": "KERI10JSON00012d_", "i": "EDGhJ8V1tuwH55Bk0fBFe9L0za2BUNOt2FX4GUeOLNHQ", "s": "0", "d": "ENNTabgWbaNqOKLqEZdQCjxbafwwSoXNzAsE1Enq-kdk", "ri": "EoN_Ln_JpgqsIys-jDOH8oWdxgWqs7hzkDGeLWHb9vSY", "a": { "s": 3, "d": "Ex7i6wv4YzDRTO9_iHkTQSXrvLYldSd_UEjNfqia3Pqc" }, "dt": "2021-01-01T00:00:00.000000+00:00", "et": "bis" } } """ cigars = cigars if cigars is not None else [] tsgs = tsgs if tsgs is not None else [] # reply specific logic if not route.startswith("/tsn"): raise ValidationError(f"Usupported route={route} in {Ilks.rpy} " f"msg={serder.ked}.") aid = kwargs["aid"] data = serder.ked["a"] dater = Dater(dts=serder.ked["dt"]) vsr = VcStateRecord(**data) # fetch from serder to process regk = vsr.ri vci = vsr.i sn = int(vsr.s, 16) ra = vsr.ra if 's' in ra: regsn = int(ra["s"], 16) else: regsn = 0 if regk not in self.tevers or self.tevers[regk].sn < regsn: if self.reger.txnsb.escrowStateNotice(typ="credential-mre", pre=vci, aid=aid, serder=serder, diger=diger, dater=dater, cigars=cigars, tsgs=tsgs): self.cues.append(dict(kin="telquery", q=dict(ri=regk))) raise MissingRegistryError("Failure verify event = {} ".format(serder.ked)) tever = self.tevers[regk] pre = tever.pre if pre not in self.kevers: if self.reger.txnsb.escrowStateNotice(typ="credential-mae", pre=vci, aid=aid, serder=serder, diger=diger, dater=dater, cigars=cigars, tsgs=tsgs): self.cues.append(dict(kin="query", q=dict(pre=aid))) raise MissingAnchorError("Failure verify event = {} ".format(serder.ked)) # Load backers from either tsn or Kever of issuer if tever.noBackers: kevers = self.kevers[pre] baks = kevers.wits else: baks = tever.baks wats = set() for _, habr in self.db.habs.getTopItemIter(): wats |= set(habr.watchers) # not in promiscuous mode if not self.lax: # check source and ensure we should accept it if aid != pre and \ aid not in baks and \ aid not in wats: raise UntrustedKeyStateSource("transaction state notice for {} from untrusted source {} " .format(vsr.i, aid)) keys = (vci, aid,) osaider = self.reger.txnsb.current(keys=keys) # get old said if any # BADA Logic accepted = self.rvy.acceptReply(serder=serder, saider=diger, route=route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: raise UnverifiedReplyError(f"Unverified credential state reply.") ldig = self.reger.tels.get(keys=vci, on=sn) # retrieve dig of last event at sn. # Only accept key state if for last seen version of event at sn if ldig is None: # escrow because event does not yet exist in database if self.reger.txnsb.escrowStateNotice(typ="credential-ooo", pre=vci, aid=aid, serder=serder, diger=diger, dater=dater, cigars=cigars, tsgs=tsgs): self.cues.append(dict(kin="telquery", q=dict(ri=regk, i=vci))) raise OutOfOrderTxnStateError("Out of order txn state={}.".format(vsr)) tdiger = Diger(qb64=vsr.d) ldig = ldig.encode("utf-8") # retrieve last event itself of signer given sdig sraw = self.reger.tvts.get(keys=(vci, ldig)) # assumes db ensures that sraw must not be none because sdig was in KE sserder = SerderKERI(raw=sraw.encode("utf-8")) if sn < sserder.sn: raise ValidationError("Stale txn state at sn = {} with db." "".format(vsr.s)) if sserder.said != tdiger.qb64: # mismatch events problem with replay raise ValidationError("Mismatch txn state at sn = {} with db." "".format(vsr.s)) self.reger.txnsb.updateReply(aid=aid, serder=serder, diger=tdiger, dater=dater) self.cues.append(dict(kin="txnStateSaved", record=vsr))
[docs] @staticmethod def registryKey(serder): """ Utility method to extract registry key from any type of TEL serder Parameters: serder (Serder): event messate Returns: str: qb64 registry identifier """ ilk = serder.ked["t"] if ilk in (Ilks.vcp, Ilks.vrt): return serder.pre elif ilk in (Ilks.iss, Ilks.rev): return serder.ked["ri"] elif ilk in (Ilks.bis, Ilks.brv): rega = serder.ked["ra"] return rega["i"] else: raise ValidationError("invalid ilk {} for tevery event = {}".format(ilk, serder.ked))
[docs] def escrowOOEvent(self, serder, seqner, saider): """ Escrow out-of-order TEL events. Saves the serialized event, anchor and event digest in escrow for any event that is received out of order. Examples include registry rotation events, credential issuance event received before the registry inception event or a credential revocation event received before the issuance event. Parameters: serder (Serder): serder of event message seqner (Seqner): sequence number of anchoring TEL event saider (Diger) digest of anchoring TEL event """ key = dgKey(serder.preb, serder.saidb) self.reger.tvts.put(keys=key, val=serder.raw) number = Number(num=seqner.sn) diger = Diger(qb64=saider.qb64) self.reger.ancs.put(keys=key, val=(number, diger)) self.reger.oots.put(keys=serder.preb, on=serder.sn, vals=serder.saidb) logger.debug("Tever state: Escrowed our of order TEL event " "event = %s", serder.ked)
[docs] def processEscrows(self): """ Loop through escrows and process and events that may now be finalized """ try: self.processEscrowAnchorless() self.processEscrowOutOfOrders() self.reger.txnsb.processEscrowState(typ="credential-mre", processReply=self.processReplyCredentialTxnState, extype=MissingRegistryError) self.reger.txnsb.processEscrowState(typ="credential-mae", processReply=self.processReplyCredentialTxnState, extype=MissingAnchorError) self.reger.txnsb.processEscrowState(typ="credential-ooo", processReply=self.processReplyCredentialTxnState, extype=OutOfOrderTxnStateError) self.reger.txnsb.processEscrowState(typ="registry-mae", processReply=self.processReplyRegistryTxnState, extype=MissingAnchorError) self.reger.txnsb.processEscrowState(typ="registry-ooo", processReply=self.processReplyRegistryTxnState, extype=OutOfOrderTxnStateError) except Exception as ex: # log diagnostics errors etc if logger.isEnabledFor(logging.DEBUG): logger.exception("Tevery escrow process error: %s", ex.args[0]) else: logger.error("Tevery escrow process error: %s", ex.args[0])
[docs] def processEscrowOutOfOrders(self): """ Loop through out of order escrow: Process out of order events in the following way: 1. loop over event digests saved in oots 2. deserialize event out of tvts 3. read anchor information out of .ancs 4. perform process event 5. Remove event digest from oots if processed successfully or a non-out-of-order event occurs. """ for pre, sn, digb in self.reger.oots.getAllItemIter(): # (pre, snb, digb) in self.reger.getOotItemIter() pre = pre[0] try: #sn = int(snb, 16) dgkey = dgKey(pre, digb) traw = self.reger.tvts.get(keys=dgkey) if traw is None: # no event so raise ValidationError which unescrows below msg = f"OOO Missing escrowed event at dig = {bytes(digb).decode()}" logger.info("Tevery unescrow error: %s", msg) raise ValidationError(msg) tserder = SerderKERI(raw=traw.encode("utf-8")) # escrowed event bigers = self.reger.tibs.get(keys=(pre, digb)) or None couple = self.reger.ancs.get(keys=dgkey) if couple is None: msg = f"OOO Missing escrowed anchor at dig = {bytes(digb).decode()}" logger.info("Tevery unescrow error: %s", msg) raise ValidationError(msg) number, diger = couple seqner = Seqner(sn=number.num) self.processEvent(serder=tserder, seqner=seqner, saider=diger, wigers=bigers) except OutOfOrderError as ex: # still waiting on missing prior event to validate if logger.isEnabledFor(logging.TRACE): logger.trace("Tevery OOO unescrow failed: %s\n", ex.args[0]) logger.exception("Tevery OOO unescrow failed: %s\n", ex.args[0]) except Exception as ex: # log diagnostics errors etc # error other than out of order so remove from OO escrow self.reger.oots.rem(keys=pre, on=sn) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): logger.exception("Tevery OOO unescrowed: %s", ex.args[0]) else: logger.error("Tevery OOO unescrowed: %s", ex.args[0]) else: # unescrow succeeded, remove from escrow # We don't remove all escrows at pre,sn because some might be # duplicitous so we process remaining escrows in spite of found # valid event escrow. self.reger.oots.rem(keys=pre, on=sn) # removes from escrow logger.info("Tevery OOO unescrow succeeded in valid event: said=%s", tserder.said) logger.debug("Event=\n%s\n", tserder.pretty())
[docs] def processEscrowAnchorless(self): """ Process escrow of TEL events received before the anchoring KEL event. Process anchorless events in the following way: 1. loop over event digests saved in taes 2. deserialize event out of tvts 3. load backer signatures out of tibs 4. read anchor information out of ancs 5. perform process event 6. Remove event digest from oots if processed successfully or a non-anchorless event occurs. """ for pre, sn, digb in self.reger.taes.getAllItemIter(): pre = pre[0] try: dgkey = dgKey(pre, digb) traw = self.reger.tvts.get(keys=dgkey) if traw is None: # no event so raise ValidationError which unescrows below msg = f"ANC Missing escrowed event at dig = {bytes(digb).decode()}" logger.trace("Tevery unescrow error: %s", msg) raise ValidationError(msg) tserder = SerderKERI(raw=traw.encode("utf-8")) # escrowed event bigers = self.reger.tibs.get(keys=(pre, digb)) or None couple = self.reger.ancs.get(keys=dgkey) if couple is None: msg = f"ANC Missing escrowed anchor at dig = {bytes(digb).decode()}" logger.trace("Tevery unescrow error: %s", msg) raise MissingAnchorError(msg) number, diger = couple seqner = Seqner(sn=number.num) self.processEvent(serder=tserder, seqner=seqner, saider=diger, wigers=bigers) except MissingAnchorError as ex: # still waiting on missing prior event to validate if logger.isEnabledFor(logging.DEBUG): logger.exception("Tevery ANC unescrow failed: %s", ex.args[0]) else: logger.error("Tevery ANC unescrow failed: %s", ex.args[0]) except Exception as ex: # log diagnostics errors etc # error other than out of order so remove from OO escrow self.reger.taes.rem(keys=pre, on=sn) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): logger.exception("Tevery ANC unescrowed: %s", ex.args[0]) else: logger.error("Tevery ANC unescrowed: %s", ex.args[0]) else: # unescrow succeeded, remove from escrow # We don't remove all escrows at pre,sn because some might be # duplicitous so we process remaining escrows in spite of found # valid event escrow. self.reger.taes.rem(keys=pre, on=sn) # removes from escrow logger.info("Tevery ANC unescrow succeeded in valid event: said=%s", tserder.said) logger.debug("event=\n%s\n", tserder.pretty())
[docs] class rbdict(dict): """ Reger backed read through cache for registry state Subclass of dict that has db and reger as attributes and employs read through cache from db Reger.stts of registry states to reload tever from state in database when not found in memory as dict item. """ __slots__ = ('db', 'reger') # no .__dict__ just for db reference def __init__(self, *pa, **kwa): super(rbdict, self).__init__(*pa, **kwa) self.db = None self.reger = None def __getitem__(self, k): try: return super(rbdict, self).__getitem__(k) except KeyError as ex: if not self.db or not self.reger: raise ex # reraise KeyError if (rsr := self.reger.states.get(keys=k)) is None: raise ex # reraise KeyError try: tever = Tever(rsr=rsr, db=self.db, reger=self.reger) except MissingEntryError: # no kel event for keystate raise ex # reraise KeyError super(rbdict, self).__setitem__(k, tever) return tever def __setitem__(self, key, item): super(rbdict, self).__setitem__(key, item) self.reger.states.pin(keys=key, val=item.state()) def __delitem__(self, key): super(rbdict, self).__delitem__(key) self.reger.states.rem(keys=key) def __contains__(self, k): if not super(rbdict, self).__contains__(k): try: self.__getitem__(k) return True except KeyError: return False else: return True
[docs] def get(self, k, default=None): """Override of dict get method Parameters: k (str): key for dict default: default value to return if not found Returns: tever: converted from underlying dict or database """ if not super(rbdict, self).__contains__(k): return default else: return self.__getitem__(k)
[docs] def openReger(name="test", **kwa): """ Returns contextmanager generated by openLMDB but with Baser instance Parameters: name (str): registry database name **kwa (dict) keyword arguments to pass to LMDB """ return openLMDB(cls=Reger, name=name, **kwa)
[docs] class Reger(LMDBer): """ Reger sets up named sub databases for TEL registry Attributes: see superclass LMDBer for inherited attributes .tvts is named sub DB whose values are serialized TEL events dgKey DB is keyed by identifier prefix plus digest of serialized event Only one value per DB key is allowed .tels is named sub DB of transaction event log tables that map sequence numbers to serialized event digests. snKey Values are digests used to lookup event in .tvts sub DB DB is keyed by identifier prefix plus sequence number of tel event Only one value per DB key is allowed .tibs is named sub DB implemented as CesrDupSuber with klas=Siger for indexed backer signatures of event. Backers always have nontransferable identifier prefixes. The index is the offset of the backer into the backer list of the anchored management event wrt the receipted event. dgKey DB is keyed by identifier prefix plus digest of serialized event. Multiple values per key in lexicographic order. .oots is named subDB instance of OnIoDupSuber for of out of order escrowed event tables that a composite key of the form <pre><sep><on> to serialized event digests. Values are digests used to lookup event in .tvts sub DB DB is keyed by identifier prefix plus sequence number of key event Only one value per DB key is allowed .baks is named subDB instance of IoDupSuber which represents an ordered list of backers at given point in management TEL. dgKey DB is keyed by identifier prefix plus digest of serialized event More than one value per DB key is allowed .twes is named subDB instance of OnIoDupSuber for partially witnessed escrowed event tables that map key composites of the form <pre><sep><on> to serialized event digests. Values are digests used to lookup event in .tvts sub DB DB is keyed by identifier prefix plus sequence number of tel event Only one value per DB key is allowed .taes is named subDB instance of OnIoDupSuber for anchorless escrowed event tables that map a composite key of the form <pre><sep><on> to serialized event digest. Values are digests used to lookup event in .tvts sub DB DB is keyed by identifier prefix plus sequence number of tel event Only one value per DB key is allowed .ancs is a named sub DB of anchors to KEL events. Quadlet Each quadruple is concatenation of four fully qualified items of validator. These are: transferable prefix, plus latest establishment event sequence number plus latest establishment event digest, plus indexed event signature. When latest establishment event is multisig then there will be multiple quadruples one per signing key, each a dup at same db key. dgKey DB is keyed by identifier prefix plus digest of serialized event Only one value per DB key is allowed .regs is named subDB instance of Komer that maps registry names to registry keys key is habitat name str value is serialized RegistryRecord dataclass """ TailDirPath = "keri/reg" AltTailDirPath = ".keri/reg" TempPrefix = "keri_reg_"
[docs] def __init__(self, headDirPath=None, reopen=True, **kwa): """ Setup named sub databases. Inherited Parameters: name (str): directory path name differentiator for main database When system employs more than one keri database, name allows differentiating each instance by name temp (boolean,): assign to .temp True then open in temporary directory, clear on close Othewise then open persistent directory, do not clear on close headDirPath (Optional(str)): head directory pathname for main database If not provided use default .HeadDirpath mode (int): numeric os dir permissions for database directory reopen (boolean,): IF True then database will be reopened by this init 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. """ self.registries = oset() self._tevers = rbdict() self._tevers.reger = self # assign db for read through cache of tevers self._tevers.db = kwa.get("db", self) super(Reger, self).__init__(headDirPath=headDirPath, reopen=reopen, **kwa)
@property def tevers(self): """ Returns ._tevers tevers getter """ return self._tevers
[docs] def reopen(self, **kwa): """ Open sub databases Parameters: **kwa (dict): keyword arguments passed to super.reopen """ super(Reger, 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.tvts = Suber(db=self, subkey='tvts.') self.tels = OnSuber(db=self, subkey='tels.') self.ancs = CatCesrSuber(db=self, subkey='ancs.', klas=(Number, Diger)) self.baks = IoDupSuber(db=self, subkey='baks.') self.tibs = CesrDupSuber(db=self, subkey='tibs.', klas=Siger) self.oots = OnIoDupSuber(db=self, subkey='oots') self.twes = OnIoDupSuber(db=self, subkey='twes') self.taes = OnIoDupSuber(db=self, subkey='taes') self.tets = CesrSuber(db=self, subkey='tets.', klas=Dater) # Registry state made of RegStateRecord. # Each registry has registry event log keyed by registry identifier self.states = Komer(db=self, klas=RegStateRecord, subkey='stts.') #self.states = SerderSuber(db=self, subkey='stts.') # registry event state # Holds the credential self.creds = SerderSuber(db=self, subkey="creds.", klas=SerderACDC) # database of anchors to credentials. prefix is either AID with direct credential # anchor or TEL event AID (same as credential SAID) when credential uses revocation registry self.cancs = CatCesrSuber(db=self, subkey='cancs.', klas=(Prefixer, Number, Diger)) # all sad path ssgs (sad pathed indexed signature serializations) maps SAD quinkeys # given by quintuple (saider.qb64, path, prefixer.qb64, number.qb64, diger.qb64) # of credential and trans signer's key state est evt to val Siger for each # signature. self.spsgs = CesrIoSetSuber(db=self, subkey='ssgs.', klas=Siger) # all sad path scgs (sad pathed non-indexed signature serializations) maps # couple (SAD SAID, path) to couple (Verfer, Cigar) of nontrans signer of signature in Cigar # nontrans qb64 of Prefixer is same as Verfer self.spcgs = CatCesrIoSetSuber(db=self, subkey='scgs.', klas=(Verfer, Cigar)) # Index of credentials processed and saved. Indicates fully verified (even if revoked) self.saved = CesrSuber(db=self, subkey='saved.', klas=Saider) # Index of credentials by issuer. My credentials issued, key == hab.pre self.issus = CesrDupSuber(db=self, subkey='issus.', klas=Saider) # Index of credentials by subject. My credentials received, key == hab.pre self.subjs = CesrDupSuber(db=self, subkey='subjs.', klas=Saider) # Index of credentials by schema self.schms = CesrDupSuber(db=self, subkey='schms.', klas=Saider) # Missing reegistry escrow self.mre = CesrSuber(db=self, subkey='mre.', klas=Dater) # Broken chain escrow self.mce = CesrSuber(db=self, subkey='mce.', klas=Dater) # Missing schema escrow self.mse = CesrSuber(db=self, subkey='mse.', klas=Dater) # Collection of sub-dbs for persisting Registry Txn State Notices self.txnsb = Broker(db=self, subkey="txn.") # registry keys keyed by Registry name self.regs = Komer(db=self, subkey='regs.', klas=RegistryRecord, ) # TEL partial witness escrow self.tpwe = CatCesrIoSetSuber(db=self, subkey='tpwe.', klas=(Prefixer, Number, Diger)) # TEL multisig anchor escrow self.tmse = CatCesrIoSetSuber(db=self, subkey='tmse.', klas=(Prefixer, Number, Diger)) # TEL event dissemination escrow self.tede = CatCesrIoSetSuber(db=self, subkey='tede.', klas=(Prefixer, Number, Saider)) # Completed TEL event self.ctel = CesrSuber(db=self, subkey='ctel.', klas=Saider) # Credential Missing Signature Escrow self.cmse = SerderSuber(db=self, subkey="cmse.", klas=SerderACDC) # Completed Credentials self.ccrd = SerderSuber(db=self, subkey="ccrd.", klas=SerderACDC) return self.env
[docs] def cloneCreds(self, saids, db): """ Returns fully expanded credential with chained credentials attached. Parameters: saids (list): of Saider objects: db (Baser): baser object to load schema Returns: list: fully hydrated credentials with full chains provided """ from ..app import serialize creds = [] for saider in saids: key = saider.qb64 creder, prefixer, number, asaider = self.cloneCred(said=key) atc = bytearray(serialize(creder, prefixer, number, saider)) del atc[0:creder.size] regk = creder.regid status = self.tevers[regk].vcState(saider.qb64) schemer = db.schema.get(creder.schema) iss = bytearray(self.cloneTvtAt(creder.said, sn=0)) iserder = SerderKERI(raw=iss) issatc = bytes(iss[iserder.size:]) del iss[0:iserder.size] if status.et in [Ilks.rev, Ilks.brv]: rev = bytearray(self.cloneTvtAt(creder.said, sn=1)) rserder = SerderKERI(raw=rev) revatc = bytes(rev[rserder.size:]) del rev[0:rserder.size] chainSaids = [] for k, p in (creder.edge.items() if creder.edge is not None else {}): if k == "d": continue if not isinstance(p, dict): continue chainSaids.append(Saider(qb64=p["n"])) chains = self.cloneCreds(chainSaids, db) cred = dict( sad=creder.sad, atc=atc.decode("utf-8"), iss=iserder.sad, issatc=issatc.decode("utf-8"), rev=rserder.sad if status.et in [Ilks.rev, Ilks.brv] else None, revatc=revatc.decode("utf-8") if status.et in [Ilks.rev, Ilks.brv] else None, pre=creder.issuer, schema=schemer.sed, chains=chains, status=asdict(status), anchor=dict( pre=prefixer.qb64, sn=number.sn, d=asaider.qb64 ) ) ctr = Counter(qb64b=iss, strip=True, version=Vrsn_1_0) if ctr.code == CtrDex_1_0.AttachmentGroup: ctr = Counter(qb64b=iss, strip=True, version=Vrsn_1_0) if ctr.code == CtrDex_1_0.SealSourceCouples: Number(qb64b=iss, strip=True) saider = Saider(qb64b=iss) anc = db.cloneEvtMsg(pre=creder.issuer, fn=0, dig=saider.qb64b) aserder = SerderKERI(raw=anc) ancatc = bytes(anc[aserder.size:]) cred['anc'] = aserder.sad cred['ancatc'] = ancatc.decode("utf-8"), if status.et in [Ilks.rev, Ilks.brv]: ctr = Counter(qb64b=rev, strip=True, version=Vrsn_1_0) if ctr.code == CtrDex_1_0.AttachmentGroup: ctr = Counter(qb64b=rev, strip=True, version=Vrsn_1_0) if ctr.code == CtrDex_1_0.SealSourceCouples: Number(qb64b=rev, strip=True) saider = Saider(qb64b=rev) anc = db.cloneEvtMsg(pre=creder.issuer, fn=0, dig=saider.qb64b) aserder = SerderKERI(raw=anc) ancatc = bytes(anc[aserder.size:]) cred['revanc'] = aserder.sad cred['revancatc'] = ancatc.decode("utf-8"), creds.append(cred) return creds
[docs] def logCred(self, creder, prefixer, number, diger): """ Save the base credential and seals (est evt+sigs quad) with no indices. Parameters: creder (Creder): that contains the credential to process prefixer (Prefixer): prefix (AID or TEL) of event anchoring credential number (Number): sequence number of event anchoring credential diger (Diger): digest of anchoring event for credential """ key = creder.said self.cancs.pin(keys=key, val=[prefixer, number, diger]) self.creds.put(keys=key, val=creder)
[docs] def cloneCred(self, said): """ Load base credential and CESR proof signatures from database. Base credential and all signatures are returned from the credential data store. If root is specified, all signatures are transposed to have that path as the root. This is used to embed the credential in another SAD at the location of the specified root. Parameters: said(str or bytes): qb64 SAID of credential """ creder = self.creds.get(keys=(said,)) if creder is None: raise MissingEntryError(f"no credential found with said {said}") prefixer, number, saider = self.cancs.get(keys=(said,)) return creder, prefixer, number, saider
[docs] def clonePreIter(self, pre, fn=0): """ Iterator of first seen event messages Returns iterator of first seen event messages with attachments for the TEL prefix pre starting at fir`st seen order number, fn. Essentially a replay in first seen order with attachments Parameters: pre (bytes): qb64 identifier prefix of registry state TEL fn (int): first seen ordinal Returns: iterator: bytearray per serializeed event msg """ if hasattr(pre, 'encode'): pre = pre.encode("utf-8") for _, fn, dig in self.tels.getAllItemIter(keys=pre, on=fn): msg = self.cloneTvt(pre, dig) yield msg
def cloneTvtAt(self, pre, sn=0): snkey = snKey(pre, sn) dig = self.tels.get(keys=pre, on=sn) return self.cloneTvt(pre, dig) def cloneTvt(self, pre, dig): msg = bytearray() # message atc = bytearray() # attachments dgkey = dgKey(pre, dig) # get message if not (raw := self.tvts.get(keys=dgkey)): raise MissingEntryError("Missing event for dig={}.".format(dig)) msg.extend(raw.encode("utf-8")) # add indexed backer signatures to attachments if tibs := self.tibs.get(keys=(pre, dig)): atc.extend(Counter(Codens.WitnessIdxSigs, count=len(tibs), version=Vrsn_1_0).qb64b) for tib in tibs: atc.extend(tib.qb64b) # add authorizer (delegator/issure) source seal event couple to attachments couple = self.ancs.get(keys=dgkey) if couple is not None: number, diger = couple seqner = Seqner(sn=number.sn) saider = Saider(qb64=diger.qb64) atc.extend(Counter(Codens.SealSourceCouples, count=1, version=Vrsn_1_0).qb64b) atc.extend(seqner.qb64b) atc.extend(saider.qb64b) # prepend pipelining counter to attachments if len(atc) % 4: raise ValueError("Invalid attachments size={}, nonintegral" " quadlets.".format(len(atc))) pcnt = Counter(Codens.AttachmentGroup, count=(len(atc) // 4), version=Vrsn_1_0).qb64b msg.extend(pcnt) msg.extend(atc) return msg
[docs] def sources(self, db, creder): """ Returns raw bytes of any source ('e') credential that is in our database Parameters: db (LMDBer): table to search creder (Creder): root credential Returns: list: credential sources as resolved from `e` in creder.crd """ chains = creder.edge if creder.edge is not None else {} saids = [] for key, source in chains.items(): if key == 'd': continue if not isinstance(source, dict): continue saids.append(source['n']) sources = [] for said in saids: screder, prefixer, number, saider = self.cloneCred(said=said) atc = bytearray(Counter(Codens.SealSourceTriples, count=1, version=Vrsn_1_0).qb64b) atc.extend(prefixer.qb64b) atc.extend(number.qb64b) atc.extend(saider.qb64b) sources.append((screder, atc)) sources.extend(self.sources(db, screder)) return sources
[docs] def buildProof(prefixer, seqner, diger, sigers): """ Create CESR proof attachment from the quadlet of seal plus signatures on the credential Parameters: prefixer (Prefixer) Identifier of the issuer of the credential seqner (Seqner) is the sequence number of the event used to sign the credential diger (Diger) is the digest of the event used to sign the credential sigers (list) are the cryptographic signatures on the credential """ prf = bytearray() prf.extend(Counter(Codens.TransIdxSigGroups, count=1, version=Vrsn_1_0).qb64b) prf.extend(prefixer.qb64b) prf.extend(seqner.qb64b) prf.extend(diger.qb64b) prf.extend(Counter(Codens.ControllerIdxSigs, count=len(sigers), version=Vrsn_1_0).qb64b) for siger in sigers: prf.extend(siger.qb64b) return prf
[docs] def messagize(creder, proof): """ Create a CESR message format with proof attachment for credential Parameters creder (Creder): instance of credential proof (str): CESR proof attachment Returns: bytearray: serialized credential with attached proof """ craw = bytearray(creder.raw) if len(proof) % 4: raise ValueError("Invalid attachments size={}, nonintegral" " quadlets.".format(len(proof))) craw.extend(Counter(Codens.AttachmentGroup, count=(len(proof) // 4), version=Vrsn_1_0).qb64b) craw.extend(proof) return craw