Source code for keri.core.eventing

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

"""
import datetime
import json
import logging
from collections import namedtuple
from dataclasses import dataclass, astuple, asdict, field
from urllib.parse import urlsplit
from math import ceil
from ordered_set import OrderedSet as oset
from hio.help import decking

from . import coring, serdering
from .coring import (versify, Serials, Ilks, MtrDex, PreDex, DigDex,
                     NonTransDex, CtrDex, Counter,
                     Number, Seqner, Siger, Cigar, Dater, Indexer, IdrDex,
                     Verfer, Diger, Prefixer, Tholder, Saider)
from . import serdering
from .. import help
from .. import kering
from ..db import basing, dbing
from ..db.basing import KeyStateRecord, StateEERecord
from ..db.dbing import dgKey, snKey, fnKey, splitKeySN, splitKey

from ..kering import (MissingEntryError,
                      ValidationError, DerivationError, MissingSignatureError,
                      MissingWitnessSignatureError, UnverifiedReplyError,
                      MissingDelegationError, OutOfOrderError,
                      LikelyDuplicitousError, UnverifiedWitnessReceiptError,
                      UnverifiedReceiptError, UnverifiedTransferableReceiptError, QueryNotFoundError)
from ..kering import Version, Versionage
from ..kering import (ICP_LABELS, DIP_LABELS, ROT_LABELS, DRT_LABELS, IXN_LABELS,
                       RPY_LABELS)

from ..help import helping

logger = help.ogler.getLogger()

EscrowTimeoutPS = 3600  # seconds for partial signed escrow timeout

MaxIntThold = 2 ** 32 - 1

[docs] @dataclass(frozen=True) class TraitCodex: """ TraitCodex is codex of inception configuration trait code strings Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ EstOnly: str = 'EO' # Only allow establishment events DoNotDelegate: str = 'DND' # Dot not allow delegated identifiers NoBackers: str = 'NB' # Do not allow any registrar backers Backers: str = 'RB' # Registrar backer provided in Registrar seal def __iter__(self): return iter(astuple(self))
TraitDex = TraitCodex() # Make instance # Location of last establishment key event: sn is int, dig is qb64 digest LastEstLoc = namedtuple("LastEstLoc", 's d') # for the following Seal namedtuples use the ._asdict() method to convert to dict # when using in events # to convert seal namedtuple to dict use namedtuple._asdict() # seal == SealEvent(i="abc",s="1",d="efg") # sealdict =seal._asdict() # to convet dict to namedtuple use ** unpacking as in seal = SealDigest(**sealdict) # to check if dict of seal matches fields of associted namedtuple # if tuple(sealdict.keys()) == SealEvent._fields: # Digest Seal: uniple (d,) # d = digest qb64 of data (usually SAID) SealDigest = namedtuple("SealDigest", 'd') # Root Seal: uniple (rd,) # rd = Merkle tree root digest qb64 digest of anchored (sealed) data in Merkle tree SealRoot = namedtuple("SealRoot", 'rd') # Backer Seal: couple (bi, d) # bi = pre qb64 backer nontrans identifier prefix # d = digest qb64 of backer metadata anchored to event usually SAID of data SealBacker = namedtuple("SealBacker", 'bi d') # Event Seal: triple (i, s, d) # i = pre is qb64 of identifier prefix of KEL for event, # s = sn of event as lowercase hex string no leading zeros, # d = SAID digest qb64 of event SealEvent = namedtuple("SealEvent", 'i s d') # Last Estalishment Event Seal: uniple (i,) # i = pre is qb64 of identifier prefix of KEL from which to get last est, event # used to indicate to get the latest keys available from KEL for 'i' SealLast = namedtuple("SealLast", 'i') # Establishment Event for Source of Message: duple (s, d) # s = sn of event as lowercase hex string no leading zeros, # d = SAID digest qb64 of event # the pre is provided in the 'i' field of the message itself which is the qb64 # of identifier prefix of KEL from which to get est, event given by 's d' # use SealSourceCouples count code for attachment SealEst = namedtuple("SealEst", 's d') # State (latest current) Event: triple (s, t, d) # s = sn of latest event as lowercase hex string no leading zeros, # t = message type of latest event (ilk) # d = SAID digest qb64 of latest event StateEvent = namedtuple("StateEvent", 's t d') # State (latest current) Establishment Event: quadruple (s, d, br, ba) # s = sn of latest est event as lowercase hex string no leading zeros, # d = SAID digest qb64 of latest establishment event # br = backer (witness) remove list (cuts) from latest est event # ba = backer (witness) add list (adds) from latest est event StateEstEvent = namedtuple("StateEstEvent", 's d br ba')
[docs] @dataclass(frozen=True) class ColdCodex: """ ColdCodex is codex of cold stream start tritets of first byte Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. First three bits: 0o0 = 000 free 0o1 = 001 cntcode B64 0o2 = 010 opcode B64 0o3 = 011 json 0o4 = 100 mgpk 0o5 = 101 cbor 0o6 = 110 mgpk 007 = 111 cntcode or opcode B2 status is one of ('evt', 'txt', 'bny' ) 'evt' if tritet in (ColdDex.JSON, ColdDex.MGPK1, ColdDex.CBOR, ColdDex.MGPK2) 'txt' if tritet in (ColdDex.CtB64, ColdDex.OpB64) 'bny' if tritet in (ColdDex.CtOpB2,) otherwise raise ColdStartError x = bytearray([0x2d, 0x5f]) x == bytearray(b'-_') x[0] >> 5 == 0o1 True """ Free: int = 0o0 # not taken CtB64: int = 0o1 # CountCode Base64 OpB64: int = 0o2 # OpCode Base64 JSON: int = 0o3 # JSON Map Event Start MGPK1: int = 0o4 # MGPK Fixed Map Event Start CBOR: int = 0o5 # CBOR Map Event Start MGPK2: int = 0o6 # MGPK Big 16 or 32 Map Event Start CtOpB2: int = 0o7 # CountCode or OpCode Base2 def __iter__(self): return iter(astuple(self))
ColdDex = ColdCodex() # Make instance Coldage = namedtuple("Coldage", 'msg txt bny') # stream cold start status Colds = Coldage(msg='msg', txt='txt', bny='bny') # Future make Cues dataclasses instead of dicts. Dataclasses so may be converted # to/from dicts easily example: dict(kin="receipt", serder=serder)
[docs] def simple(n): """ Returns int as simple majority of n when n >=1 otherwise returns 0 Parameters: n is int total number of elements """ return min(max(0, n), (max(0, n) // 2) + 1)
[docs] def ample(n, f=None, weak=True): """ Returns int as sufficient immune (ample) majority of n when n >=1 otherwise returns 0 Parameters: n is int total number of elements f is int optional fault number weak is Boolean If f is not None and weak is True then minimize m for f weak is False then maximize m for f that satisfies n >= 3*f+1 Else weak is True then find maximum f and minimize m weak is False then find maximum f and maximize m n,m,f are subject to f >= 1 if n > 0 n >= 3*f+1 (n+f+1)/2 <= m <= n-f """ n = max(0, n) # no negatives if f is None: f1 = max(1, max(0, n - 1) // 3) # least floor f subject to n >= 3*f+1 f2 = max(1, ceil(max(0, n - 1) / 3)) # most ceil f subject to n >= 3*f+1 if weak: # try both fs to see which one has lowest m return min(n, ceil((n + f1 + 1) / 2), ceil((n + f2 + 1) / 2)) else: return min(n, max(0, n - f1, ceil((n + f1 + 1) / 2))) else: f = max(0, f) m1 = ceil((n + f + 1) / 2) m2 = max(0, n - f) if m2 < m1 and n > 0: raise ValueError("Invalid f={} is too big for n={}.".format(f, n)) if weak: return min(n, m1, m2) else: return min(n, max(m1, m2))
# Utility functions for extracting groups of primitives # bytearray of memoryview makes a copy so does not delete underlying data # behind memory view but del on bytearray itself does delete bytearray
[docs] def deWitnessCouple(data, strip=False): """ Returns tuple of (diger, wiger) extracted from bytes or bytearray that hold concatenated data couple where: diger is Diger instance wiger is Siger instance Couple is dig+wig where: dig is receipted event digest wig is indexed signature made with key pair derived from witness nontrans identifier prefix from witness list. Index is offset into witness list of latest establishment event for receipted event. Parameters: data is couple of bytes concatenation of dig+wig from receipt deletive is Boolean True means delete from data each part as parsed Only useful if data is bytearray from front of stream Witness couple is used for escrows of unverified witness recipts signed by nontransferable witness prefix keys with indexed signatures where index is offset into associated witness list. At time of escrow receipted event may not be in KEL so need the dig to look up event and then look up witness list from key state. """ if isinstance(data, memoryview): data = bytes(data) if hasattr(data, "encode"): data = data.encode("utf-8") # convert to bytes diger = Diger(qb64b=data, strip=strip) if not strip: data = data[len(diger.qb64b):] wiger = Siger(qb64b=data, strip=strip) return (diger, wiger)
[docs] def deReceiptCouple(data, strip=False): """ Returns tuple of (prefixer, cigar) from concatenated bytes or bytearray of data couple made up of qb64 or qb64b versions of pre+cig where: pre is nontransferable identifier prefix of receiptor cig is nonindexed signature made with key pair derived from pre Couple is used for receipts signed by nontransferable prefix keys Parameters: data is couple of bytes concatenation of pre+sig from receipt strip is Boolean True means delete from data each part as parsed Only useful if data is bytearray from front of stream Raises error if not bytearray """ if isinstance(data, memoryview): data = bytes(data) if hasattr(data, "encode"): data = data.encode("utf-8") # convert to bytes prefixer = Prefixer(qb64b=data, strip=strip) if not strip: data = data[len(prefixer.qb64b):] cigar = Cigar(qb64b=data, strip=strip) return (prefixer, cigar)
[docs] def deSourceCouple(data, strip=False): """ Returns tuple of (seqner, saider) from concatenated bytes or bytearray of data couple made up of qb64 or qb64b versions of snu+dig where: snu is sn of delegator/issuer source event dig is digest of delegator/issuer source event Couple is used for delegated/issued event attachment of delegator/issuer evt Parameters: data is couple of bytes concatenation of pre+sig from receipt strip is Boolean True means delete from data each part as parsed Only useful if data is bytearray from front of stream Raises error if not bytearray """ if isinstance(data, memoryview): data = bytes(data) if hasattr(data, "encode"): data = data.encode("utf-8") # convert to bytes seqner = Seqner(qb64b=data, strip=strip) if not strip: data = data[len(seqner.qb64b):] saider = Saider(qb64b=data, strip=strip) return (seqner, saider)
[docs] def deReceiptTriple(data, strip=False): """ Returns tuple of (diger, prefixer, cigar) from concatenated bytes or bytearray of data triple made up of qb64 or qb64b versions of dig+pre+cig where: dig is receipted event digest pre is nontransferable identifier prefix of receiptor cig is nonindexed signature made with key pair derived from pre Triple is used for escrows of unverified receipts signed by nontransferable prefix keys Parameters: data is triple of bytes concatenation of dig+pre+cig from receipt deletive is Boolean True means delete from data each part as parsed Only useful if data is bytearray from front of stream """ if isinstance(data, memoryview): data = bytes(data) if hasattr(data, "encode"): data = data.encode("utf-8") # convert to bytes saider = Saider(qb64b=data, strip=strip) if not strip: data = data[len(saider.qb64b):] prefixer = Prefixer(qb64b=data, strip=strip) if not strip: data = data[len(prefixer.qb64b):] cigar = Cigar(qb64b=data, strip=strip) return (saider, prefixer, cigar)
[docs] def deTransReceiptQuadruple(data, strip=False): """ Returns tuple (quadruple) of (prefixer, seqner, diger, siger) from concatenated bytes or bytearray of quadruple made up of qb64 or qb64b versions of spre+ssnu+sdig+sig. Quadruple is used for receipts signed by transferable prefix keys. Recept for event that is in kel where event is given by context or key Parameters: quadruple is bytes concatenation of pre+snu+dig+sig from receipt deletive is Boolean True means delete from data each part as parsed Only useful if data is bytearray from front of stream """ if isinstance(data, memoryview): data = bytes(data) if hasattr(data, "encode"): data = data.encode("utf-8") # convert to bytes prefixer = Prefixer(qb64b=data, strip=strip) if not strip: data = data[len(prefixer.qb64b):] seqner = Seqner(qb64b=data, strip=strip) if not strip: data = data[len(seqner.qb64b):] saider = Saider(qb64b=data, strip=strip) if not strip: data = data[len(saider.qb64b):] siger = Siger(qb64b=data, strip=strip) return (prefixer, seqner, saider, siger)
[docs] def deTransReceiptQuintuple(data, strip=False): """ Returns tuple of (ediger, seal prefixer, seal seqner, seal diger, siger) from concatenated bytes or bytearray of quintuple made up of qb64 or qb64b versions of quntipuple given by concatenation of edig+spre+ssnu+sdig+sig. Quintuple is used for unverified escrows of validator receipts signed by transferable prefix keys. Receipt for event that is not yet in KEL where event is given by event digest (ediger) Parameters: quintuple is bytes concatenation of edig+spre+ssnu+sdig+sig from receipt deletive is Boolean True means delete from data each part as parsed Only useful if data is bytearray from front of stream """ if isinstance(data, memoryview): data = bytes(data) if hasattr(data, "encode"): data = data.encode("utf-8") # convert to bytes esaider = Saider(qb64b=data, strip=strip) # diger of receipted event if not strip: data = data[len(esaider.qb64b):] sprefixer = Prefixer(qb64b=data, strip=strip) # prefixer of recipter if not strip: data = data[len(sprefixer.qb64b):] sseqner = Seqner(qb64b=data, strip=strip) # seqnumber of receipting event if not strip: data = data[len(sseqner.qb64b):] ssaider = Saider(qb64b=data, strip=strip) # diger of receipting event if not strip: data = data[len(ssaider.qb64b):] siger = Siger(qb64b=data, strip=strip) # indexed siger of event return esaider, sprefixer, sseqner, ssaider, siger
[docs] def validateSN(sn, inceptive=None): """ Returns: sn (int): converted from sn hex str Raises ValueError if invalid sn Parameters: sn (str): hex char sequence number of event or seal in an event inceptive(bool): Check sn value and raise ValueError if invalid None means check for sn < 0 True means check for sn != 0 False means check for sn < 1 """ if len(sn) > 32: raise ValueError("Invalid sn = {} too large.".format(sn)) try: sn = int(sn, 16) except Exception as ex: raise ValueError("Invalid sn = {}.".format(sn)) if inceptive is not None: if inceptive: if sn != 0: raise ValidationError("Nonzero sn = {} for inception evt." "".format(sn)) else: if sn < 1: raise ValidationError("Zero or less sn = {} for non-inception evt." "".format(sn)) else: if sn < 0: raise ValidationError("Negative sn = {} for event.".format(sn)) return sn
[docs] def verifySigs(raw, sigers, verfers): """ Returns tuple of (vsigers, vindices) where: vsigers is list of unique verified sigers with assigned verfer vindices is list of indices from those verified sigers The returned vsigers and vindices may be used for threshold validation Assigns appropriate verfer from verfers to each siger based on siger index If no signatures verify then sigers and indices are empty Parameters: raw (bytes) signed data sigers is list of indexed Siger instances (signatures) verfers is list of Verfer instance (public keys) """ if sigers is None: sigers = [] # Ensure no duplicate sigers by using set math on sigers' sigs otherwise # indices count for threshold will be erroneous. Does not modify in place # passed in sigers list, but instead depends on caller to use indices to # modify its copy to filter out unverifiable or duplicate sigers usigs = oset([siger.qb64 for siger in sigers]) usigers = [Siger(qb64=sig) for sig in usigs] # verify indexes of attached signatures against verifiers and assign # verfer to each siger for siger in usigers: if siger.index >= len(verfers): logger.info("Skipped sig: Index=%s to large.\n", siger.index) siger.verfer = verfers[siger.index] # assign verfer # create lists of unique verified signatures and indices vindices = [] vsigers = [] for siger in usigers: if siger.verfer.verify(siger.raw, raw): vindices.append(siger.index) vsigers.append(siger) return (vsigers, vindices)
[docs] def validateSigs(serder, sigers, verfers, tholder): """ Validates signatures given by sigers using keys given by verfers on msg given by serder subject to threshold given by tholder. Returns subset of valid signatures for storage. Returns: result (tuple): (sigers, valid) where: sigers (list): subset of of provided sigers of verified signatures on serder using verfers valid (bool): True means threshold from tholder satisfied by sigers, False otherwise. Parameters: serder (SerderKERI): instance of message sigers (Iterable): Siger instances of indexed signatures. Index is offset into verfers list each providing verification key verfers (Iterable): Verfer instances of keys tholder (Tholder): instance of signing threshold (sith) seqner is Seqner instance of delegating event sequence number. If this event is not delegated then seqner is ignored diger is Diger instance of of delegating event digest. If this event is not delegated then diger is ignored """ valid = False if len(verfers) < tholder.size: raise ValidationError("Invalid sith = {} for keys = {}." "".format(tholder.sith, [verfer.qb64 for verfer in verfers])) # get unique verified sigers and indices lists from sigers list sigers, indices = verifySigs(raw=serder.raw, sigers=sigers, verfers=verfers) # sigers now have .verfer assigned # check if satisfies threshold for fully signed if not indices: # must have a least one verified sig raise ValidationError("No verified signatures for message={}." "".format(serder.ked)) valid = tholder.satisfy(indices) return (sigers, valid)
[docs] def fetchTsgs(db, saider, snh=None): """ Fetch tsgs for saider from .db.ssgs. When sn then only fetch if sn <= snh Returns: tsgs (list): of tsg quadruple of form (prefixer, seqner, diger, sigers) where: prefixer (Prefixer): instance trans signer aid, seqner (Seqner): of sn of trans signer key state est event diger (Diger): of digest of trans signer key state est event sigers (list): of Siger instances of indexed signatures Parameters: db: (Cesr saider (Saider): instance of said for reply SAD to which signatures are attached snh (str): 32 char zero pad lowercase hex of sequence number f"{sn:032x}" """ klases = (coring.Prefixer, coring.Seqner, coring.Diger) args = ("qb64", "snh", "qb64") tsgs = [] # transferable signature groups sigers = [] old = None # empty keys for keys, siger in db.getItemIter(keys=(saider.qb64, "")): triple = keys[1:] if triple != old: # new tsg if snh is not None and triple[1] > snh: # only lower sn break if sigers: # append tsg made for old and sigers tsgs.append((*helping.klasify(sers=old, klases=klases, args=args), sigers)) sigers = [] old = triple sigers.append(siger) if sigers and old: tsgs.append((*helping.klasify(sers=old, klases=klases, args=args), sigers)) return tsgs
[docs] def state(pre, sn, pig, dig, fn, eilk, keys, eevt, stamp=None, # default current datetime sith=None, # default based on keys ndigs=None, nsith=None, toad=None, # default based on wits wits=None, # default to [] cnfg=None, # default to [] dpre=None, version=Version, kind=Serials.json, intive = False, ): """ Returns instance of KeyStateRecord in support of key state notification messages. Utility function to automate creation embedded key static notices Parameters: pre (str): identifier prefix qb64 sn (int): sequence number of latest event pig (str): SAID qb64 of prior event dig (str): SAID qb64 of latest (current) event fn (int): first seen ordinal number of latest event eilk (str): event (message) type (ilk) of latest (current) event keys (list): qb64 signing keys eevt (StateEstEvent): namedtuple (s,d,wr,wa) for latest est event s = sn of est event d = SAID of est event wr = witness remove list (cuts) wa = witness add list (adds) stamp (str | None): date-time-stamp RFC-3339 profile of ISO-8601 datetime of creation of message or data sith sith (int | str | list | None): current signing threshold input to Tholder ndigs (list | None): current signing key digests qb64 nsith int | str | list | None): next signing threshold input to Tholder toad (int | str | None): witness threshold number if str then hex str wits (list | None): prior witness identifier prefixes qb64 cnfg (list | None): strings from TraitDex configuration trait strings dpre (str | None): identifier prefix qb64 delegator if any If None then dpre in state is empty "" version (Version): KERI protocol version string kind (str): serialization kind from Serials intive (bool): True means sith, nsith, and toad are serialized as ints instead of hex str when numeric threshold """ sner = Number(num=sn) # raises InvalidValueError if sn < 0 fner = Number(num=fn) # raises InvalidValueError if fn < 0 if eilk not in (Ilks.icp, Ilks.rot, Ilks.ixn, Ilks.dip, Ilks.drt): raise ValueError(f"Invalid event type et={eilk} in key state.") if stamp is None: stamp = helping.nowIso8601() if sith is None: sith = "{:x}".format(max(1, ceil(len(keys) / 2))) tholder = Tholder(sith=sith) if tholder.num is not None and tholder.num < 1: raise ValueError(f"Invalid sith = {tholder.num} less than 1.") if tholder.size > len(keys): raise ValueError(f"Invalid sith = {tholder.num} for keys = {keys}") if ndigs is None: ndigs = [] if nsith is None: nsith = max(0, ceil(len(ndigs) / 2)) ntholder = Tholder(sith=nsith) if ntholder.num is not None and ntholder.num < 0: raise ValueError(f"Invalid nsith = {ntholder.num} less than 0.") if ntholder.size > len(ndigs): raise ValueError(f"Invalid nsith = {ntholder.num} for keys = {ndigs}") wits = wits if wits is not None else [] witset = oset(wits) if len(witset) != len(wits): raise ValueError(f"Invalid wits = {wits}, has duplicates.") if toad is None: if not witset: toad = 0 else: toad = max(1, ceil(len(witset) / 2)) if toad is None: if not witset: toad = 0 else: # compute default f and m for len(wits) toad = ample(len(witset)) toader = Number(num=toad) if witset: if toader.num < 1 or toader.num > len(witset): # out of bounds toad raise ValueError(f"Invalid toad = {toader.num} for wits = {witset}") else: if toader.num != 0: # invalid toad raise ValueError(f"Invalid toad = {toader.num} for wits = {witset}") if not eevt or not isinstance(eevt, StateEstEvent): raise ValueError(f"Missing or invalid latest est event = {eevt} for key " f"state.") eesner = Number(numh=eevt.s) # if not whole number raises InvalidValueError # cuts is relative to prior wits not current wits provided here cuts = eevt.br if eevt.br is not None else [] cutset = oset(cuts) if len(cutset) != len(cuts): # duplicates in cuts raise ValueError(f"Invalid cuts = {cuts}, has " f"duplicates, in latest est event, .") # adds is relative to prior wits not current wits provided here adds = eevt.ba if eevt.ba is not None else [] addset = oset(adds) if len(addset) != len(adds): # duplicates in adds raise ValueError(f"Invalid adds = {adds}, has duplicates," f" in latest est event,.") if cutset & addset: # non empty intersection raise ValueError(f"Intersecting cuts = {cuts} and adds = {adds} in " f"latest est event.") ksr = basing.KeyStateRecord( vn=list(version), # version number as list [major, minor] i=pre, # qb64 prefix s=sner.numh, # lowercase hex string no leading zeros p=pig, d=dig, f=fner.numh, # lowercase hex string no leading zeros dt=stamp, et=eilk, kt=(tholder.num if intive and tholder.num is not None and tholder.num <= MaxIntThold else tholder.sith), k=keys, # list of qb64 nt=(ntholder.num if intive and ntholder.num is not None and ntholder.num <= MaxIntThold else ntholder.sith), n=ndigs, bt=toader.num if intive and toader.num <= MaxIntThold else toader.numh, b=wits, # list of qb64 may be empty c=cnfg if cnfg is not None else [], ee=StateEERecord._fromdict(eevt._asdict()), # latest est event dict di=dpre if dpre is not None else "", ) return ksr # return KeyStateRecord use asdict(ksr) to get dict version
[docs] def incept(keys, *, isith=None, ndigs=None, nsith=None, toad=None, wits=None, cnfg=None, data=None, version=Version, kind=Serials.json, code=None, intive=False, delpre=None, ): """ Returns serder of inception event message. Utility function to automate creation of inception events. Parameters: keys (list): current signing keys qb64 sith (int | str | list | None): current signing threshold input to Tholder ndigs (list | None): current signing key digests qb64 nsith int | str | list | None): next signing threshold input to Tholder toad (int | str | None): witness threshold number if str then hex str wits (list | None): witness identifier prefixes qb64 cnfg (list | None): configuration traits from TraitDex data (list | None): seal dicts version (Version): KERI protocol version string kind (str): serialization kind from Serials code (str | None): derivation code for computed prefix intive (bool): True means sith, nsith, and toad are serialized as ints not hex str when numeric threshold. Most compact JSON representation when Numbers are small because no quotes. Number accepts both. delpre (str | None): delegator identifier prefix qb64. When not None makes this a msg type "dip", delegated inception event. """ vs = versify(version=version, kind=kind, size=0) ilk = Ilks.icp if delpre is None else Ilks.dip # inception or delegated inception sner = Number(num=0) # sn for incept must be 0 if isith is None: isith = max(1, ceil(len(keys) / 2)) tholder = Tholder(sith=isith) if tholder.num is not None and tholder.num < 1: raise ValueError(f"Invalid sith = {tholder.num} less than 1.") if tholder.size > len(keys): raise ValueError(f"Invalid sith = {tholder.num} for keys = {keys}") if ndigs is None: ndigs = [] if nsith is None: nsith = max(0, ceil(len(ndigs) / 2)) ntholder = Tholder(sith=nsith) if ntholder.num is not None and ntholder.num < 0: raise ValueError(f"Invalid nsith = {ntholder.num} less than 0.") if ntholder.size > len(ndigs): raise ValueError(f"Invalid nsith = {ntholder.num} for keys = {ndigs}") wits = wits if wits is not None else [] if len(oset(wits)) != len(wits): raise ValueError(f"Invalid wits = {wits}, has duplicates.") if toad is None: if not wits: toad = 0 else: # compute default f and m for len(wits) toad = ample(len(wits)) toader = Number(num=toad) if wits: if toader.num < 1 or toader.num > len(wits): # out of bounds toad raise ValueError(f"Invalid toad = {toader.num} for wits = {wits}") else: if toader.num != 0: # invalid toad raise ValueError(f"Invalid toad = {toader.num} for wits = {wits}") cnfg = cnfg if cnfg is not None else [] data = data if data is not None else [] ked = dict(v=vs, # version string t=ilk, d="", # qb64 SAID i="", # qb64 prefix s=sner.numh, # hex string no leading zeros lowercase kt=(tholder.num if intive and tholder.num is not None and tholder.num <= MaxIntThold else tholder.sith), k=keys, # list of qb64 nt=(ntholder.num if intive and ntholder.num is not None and ntholder.num <= MaxIntThold else ntholder.sith), n=ndigs, # list of hashes qb64 bt=toader.num if intive and toader.num <= MaxIntThold else toader.numh, b=wits, # list of qb64 may be empty c=cnfg, # list of config ordered mappings may be empty a=data, # list of seal dicts ) pre = "" saids = None if delpre is not None: # delegated inception with ilk = dip ked['di'] = delpre # SerderKERI .verify will ensure valid prefix else: # non delegated if (code is None or code not in DigDex) and len(keys) == 1: # use key[0] as default ked["i"] = keys[0] # SerderKERI .verify will ensure valid prefix if code is not None and code in PreDex: # use code to override all else saids = {'i': code} serder = serdering.SerderKERI(sad=ked, makify=True, saids=saids) serder._verify() # raises error if fails verifications return serder
#if delpre is not None: # delegated inception with ilk = dip #ked['di'] = delpre #if code is None: #code = MtrDex.Blake3_256 # force digestive #if delpre is None and code is None and len(keys) == 1: #prefixer = Prefixer(qb64=keys[0]) # defaults to not digestive code #if prefixer.digestive: #raise ValueError("Invalid code, digestive={}, must be derived from" #" ked.".format(prefixer.code)) #else: # digestive ## raises derivation error if non-empty nxt but ephemeral code #prefixer = Prefixer(ked=ked, code=code) # Derive AID from ked and code #if delpre is not None: #if not prefixer.digestive: #raise ValueError(f"Invalid derivation code = {prefixer.code} " #f"for delegation. Must be digestive") #ked["i"] = prefixer.qb64 # update pre element in ked with pre qb64 #if prefixer.digestive: #ked["d"] = prefixer.qb64 #else: #_, ked = coring.Saider.saidify(sad=ked) #return Serder(ked=ked) # return serialized ked
[docs] def delcept(keys, delpre, **kwa): """ Returns serder of delegated inception event message. Utility function to automate creation of delegated inception events. Syntactic suger that calls incept but with delpre so ilk is dip. Parameters: keys (list): current signing keys qb64 sith (int | str | list | None): current signing threshold input to Tholder ndigs (list | None): current signing key digests qb64 nsith int | str | list | None): next signing threshold input to Tholder toad (int | str | None): witness threshold number if str then hex str wits (list | None): witness identifier prefixes qb64 cnfg (list | None): configuration traits from TraitDex data (list | None): seal dicts version (Version): KERI protocol version string kind (str): serialization kind from Serials code (str | None): derivation code for computed prefix intive (bool): True means sith, nsith, and toad are serialized as ints not hex str when numeric threshold delpre (str | None): delegator identifier prefix qb64. When not None makes this a msg type "dip", delegated inception event. """ return incept(keys=keys, delpre=delpre, **kwa)
[docs] def rotate(pre, keys, dig, *, ilk=Ilks.rot, sn=1, isith=None, ndigs=None, nsith=None, toad=None, wits=None, # prior existing wits cuts=None, adds=None, data=None, version=Version, kind=Serials.json, intive = False, ): """ Returns serder of rotation event message. Utility function to automate creation of rotation events. Parameters: pre (str): identifier prefix qb64 keys (list): current signing keys qb64 dig (str): SAID of previous event qb64 ilk (str): ilk of event. Must be in (Ilks.rot, Ilks.drt) sn (int | str): sequence number int or hex str sith (int | str | list | None): current signing threshold input to Tholder ndigs (list | None): current signing key digests qb64 nsith int | str | list | None): next signing threshold input to Tholder toad (int | str | None): witness threshold number if str then hex str wits (list | None): prior witness identifier prefixes qb64 cuts (list | None): witness prefixes to cut qb64 adds (list | None): witness prefixes to add qb64 data (list | None): seal dicts version (Version): KERI protocol version string kind (str): serialization kind from Serials intive (bool): True means sith, nsith, and toad are serialized as ints instead of hex str when numeric threshold """ vs = versify(version=version, kind=kind, size=0) ilk = ilk if ilk not in (Ilks.rot, Ilks.drt): raise ValueError(f"Invalid ilk ={ilk} for rot or drt.") sner = Number(num=sn) if sner.num < 1: # sn for rotate must be >= 1 raise ValueError(f"Invalid sn = 0x{sner.numh} for rot or drt.") if isith is None: isith = max(1, ceil(len(keys) / 2)) tholder = Tholder(sith=isith) if tholder.num is not None and tholder.num < 1: raise ValueError(f"Invalid sith = {tholder.num} less than 1.") if tholder.size > len(keys): raise ValueError(f"Invalid sith = {tholder.num} for keys = {keys}") if ndigs is None: ndigs = [] if nsith is None: nsith = max(0, ceil(len(ndigs) / 2)) ntholder = Tholder(sith=nsith) if ntholder.num is not None and ntholder.num < 0: raise ValueError(f"Invalid nsith = {ntholder.num} less than 0.") if ntholder.size > len(ndigs): raise ValueError(f"Invalid nsith = {ntholder.num} for keys = {ndigs}") wits = wits if wits is not None else [] witset = oset(wits) if len(witset) != len(wits): raise ValueError(f"Invalid wits = {wits}, has duplicates.") cuts = cuts if cuts is not None else [] cutset = oset(cuts) if len(cutset) != len(cuts): raise ValueError(f"Invalid cuts = {cuts}, has duplicates.") if (witset & cutset) != cutset: # some cuts not in wits raise ValueError(f"Invalid cuts = {cuts}, not all members in wits.") adds = adds if adds is not None else [] addset = oset(adds) if len(addset) != len(adds): raise ValueError(f"Invalid adds = {adds}, has duplicates.") if witset & addset: # non empty intersection raise ValueError(f"Intersecting wits = {wits} and adds = {adds}.") if cutset & addset: # non empty intersection raise ValueError(f"Intersecting cuts = {cuts} and adds = {adds}.") newitset = (witset - cutset) | addset if len(newitset) != (len(wits) - len(cuts) + len(adds)): # redundant? raise ValueError(f"Invalid member combination among wits = {wits}, " f"cuts ={cuts}, and adds = {adds}.") if toad is None: if not newitset: toad = 0 else: # compute default f and m for len(wits) toad = ample(len(newitset)) toader = Number(num=toad) if newitset: if toader.num < 1 or toader.num > len(newitset): # out of bounds toad raise ValueError(f"Invalid toad = {toader.num} for wits = {newitset}") else: if toader.num != 0: # invalid toad raise ValueError(f"Invalid toad = {toader.num} for wits = {newitset}") ked = dict(v=vs, # version string t=ilk, d="", # qb64 SAID i=pre, # qb64 prefix s=sner.numh, # hex string no leading zeros lowercase p=dig, # SAID qb64 digest of prior event kt=(tholder.num if intive and tholder.num is not None and tholder.num <= MaxIntThold else tholder.sith), k=keys, # list of qb64 nt=(ntholder.num if intive and ntholder.num is not None and ntholder.num <= MaxIntThold else ntholder.sith), n=ndigs, # hash qual Base64 bt=toader.num if intive and toader.num <= MaxIntThold else toader.numh, br=cuts, # list of qb64 may be empty ba=adds, # list of qb64 may be empty a= data if data is not None else [], # list of seals ) serder = serdering.SerderKERI(sad=ked, makify=True) serder._verify() # raises error if fails verifications return serder
#_, ked = coring.Saider.saidify(sad=ked) #return Serder(ked=ked) # return serialized ked
[docs] def deltate(pre, keys, dig, ilk=Ilks.drt, **kwa ): """ Returns serder of delegated rotation event message. Utility function to automate creation of delegated rotation events. Syntactic suger that calls rotate but with ilk set to drt. Parameters: pre (str): identifier prefix qb64 keys (list): current signing keys qb64 dig (str): said of previous event qb64 ilk (str): ilk of event. Must be in (Ilks.rot, Ilks.drt) sn (int | str): sequence number int or hex str sith (int | str | list): current signing threshold input to Tholder ndigs (list): current signing key digests qb64 nsith int | str | list): next signing threshold input to Tholder toad (int | str ): witness threshold number if str then hex str wits (list): prior witness identifier prefixes qb64 cuts (list): witness prefixes to cut qb64 adds (list): witness prefixes to add qb64 data (list): seal dicts version (Version): KERI protocol version string kind (str): serialization kind from Serials intive (bool): True means sith, nsith, and toad are serialized as ints not hex str when numeric threshold """ return rotate(pre=pre, keys=keys, dig=dig, ilk=ilk, **kwa)
[docs] def interact(pre, dig, sn=1, data=None, version=Version, kind=Serials.json, ): """ Returns serder of interaction event message. Utility function to automate creation of interaction events. Parameters: pre is identifier prefix qb64 dig is said digest of previous event qb64 sn is int sequence number data is list of dicts of comitted data such as seals version is Version instance kind is serialization kind """ vs = versify(version=version, kind=kind, size=0) ilk = Ilks.ixn sner = Number(num=sn) if sner.num < 1: # sn for interact must be >= 1 raise ValueError(f"Invalid sn = 0x{sner.numh} for ixn.") data = data if data is not None else [] sad = dict(v=vs, # version string t=ilk, d="", i=pre, # qb64 prefix s=sner.numh, # hex string no leading zeros lowercase p=dig, # qb64 digest of prior event a=data, # list of seals ) serder = serdering.SerderKERI(sad=sad, makify=True) serder._verify() # raises error if fails verifications return serder
#_, ked = coring.Saider.saidify(sad=ked) #return Serder(ked=ked) # return serialized ked
[docs] def receipt(pre, sn, said, *, version=Version, kind=Serials.json ): """ Returns serder of event receipt message. Used for both non-trans and trans signers as determined by signature attachment type (cigar or siger) Utility function to automate creation of receipts. Parameters: pre is qb64 str of prefix of event being receipted sn is int sequence number of event being receipted said is qb64 of said of event being receipted version is Version instance of receipt kind is serialization kind of receipt """ vs = versify(version=version, kind=kind, size=0) ilk = Ilks.rct sner = Number(num=sn) if sner.num < 0: # sn for receipt must be >= 1 raise ValueError(f"Invalid sn = 0x{sner.numh} for rect.") sad = dict(v=vs, # version string t=ilk, # Ilks.rct d=said, # qb64 digest of receipted event i=pre, # qb64 prefix s=sner.numh, # hex string no leading zeros lowercase ) serder = serdering.SerderKERI(sad=sad, makify=True) serder._verify() # raises error if fails verifications return serder
[docs] def query(route="", replyRoute="", query=None, stamp=None, version=Version, kind=Serials.json): """ Returns serder of query 'qry' message. Utility function to automate creation of query messages. Parameters: 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. query (dict): query data paramaters modifiers stamp (str): date-time-stamp RFC-3339 profile of ISO-8601 datetime of creation of message version (Version): KERI message Version namedtuple instance kind (str): serialization kind value of Serials { "v" : "KERI10JSON00011c_", "t" : "qry", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "logs", "rr": "log/processor", "q" : { "i": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", "sn": "5", "dt": "2020-08-01T12:20:05.123456+00:00", } } """ vs = versify(version=version, kind=kind, size=0) ilk = Ilks.qry sad = dict(v=vs, # version string t=ilk, d="", dt=stamp if stamp is not None else helping.nowIso8601(), r=route, # resource type for single item request rr=replyRoute, q=query, ) serder = serdering.SerderKERI(sad=sad, makify=True) serder._verify() # raises error if fails verifications return serder
#_, ked = coring.Saider.saidify(sad=ked) #return Serder(ked=ked) # return serialized ked
[docs] def reply(route="", data=None, stamp=None, version=Version, kind=Serials.json): """ Returns serder of reply 'rpy' message. Utility function to automate creation of reply messages. Reply 'rpy' message is a SAD item with an associated derived SAID in its 'd' field. Parameters: route (str): '/' delimited path identifier of data flow handler (behavior) to processs the reply if any data (dict): attribute section of reply stamp (str): date-time-stamp RFC-3339 profile of ISO-8601 datetime of creation of message or data version (Version): KERI message Version namedtuple instance kind (str): serialization kind value of Serials { "v" : "KERI10JSON00011c_", "t" : "rpy", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "logs/processor", "a" : { "d": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", "i": "EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM", "name": "John Jones", "role": "Founder", } } """ label = coring.Saids.d vs = versify(version=version, kind=kind, size=0) if data is None: data = {} sad = dict(v=vs, # version string t=Ilks.rpy, d="", dt=stamp if stamp is not None else helping.nowIso8601(), r=route if route is not None else "", # route a=data if data else {}, # attributes ) serder = serdering.SerderKERI(sad=sad, makify=True) serder._verify() # raises error if fails verifications return serder
#_, sad = coring.Saider.saidify(sad=sad, kind=kind, label=label) #saider = coring.Saider(qb64=sad[label]) #if not saider.verify(sad=sad, kind=kind, label=label, prefixed=True): #raise ValidationError("Invalid said = {} for reply msg={}." #"".format(saider.qb64, sad)) #return Serder(ked=sad) # return serialized Self-Addressed Data (SAD)
[docs] def prod(route="", replyRoute="", query=None, stamp=None, version=Version, kind=Serials.json): """ Returns serder of prod, 'pro', msg to request disclosure via bare, 'bar' msg of data anchored via seal(s) on KEL for identifier prefix, pre, when given by all SAIDs given in digs list. { "v" : "KERI10JSON00011c_", "t" : "pro", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "data", "rr": "data/processor", "q": { "d":"EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM" } } """ vs = versify(version=version, kind=kind, size=0) ilk = Ilks.pro sad = dict(v=vs, # version string t=ilk, d="", dt=stamp if stamp is not None else helping.nowIso8601(), r=route, # resource type for single item request rr=replyRoute, q=query, ) serder = serdering.SerderKERI(sad=sad, makify=True) serder._verify() # raises error if fails verifications return serder
#_, ked = coring.Saider.saidify(sad=ked) #return Serder(ked=ked) # return serialized ked
[docs] def bare(route="", data=None, stamp=None, version=Version, kind=Serials.json): """ Returns serder of bare 'bar' message. Utility function to automate creation of unhiding (bareing) messages for disclosure of sealed data associated with anchored seals in a KEL. Reference to anchoring seal is provided as an attachment to bare message. Bare 'bar' message is a SAD item with an associated derived SAID in a 'd' field in side its 'a' block. Parameters: route is route path string that indicates data flow handler (behavior) to processs the exposure data is dict of dicts of comitted SADS for SAIDs in seals keyed by SAID stamp (str): date-time-stamp RFC-3339 profile of ISO-8601 datetime of creation of message or data version is Version instance kind is serialization kind { "v" : "KERI10JSON00011c_", "t" : "bar", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "sealed/processor", "a" : { "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM": { "d": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", "i": "EAoTNZH3ULvYAfSVPzhzS6baU6JR2nmwyZ-i0d8JZ5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "name": "John Jones", "role": "Founder", } } } """ vs = versify(version=version, kind=kind, size=0) sad = dict(v=vs, # version string t=Ilks.bar, d="", dt=stamp if stamp is not None else helping.nowIso8601(), r=route if route is not None else "", # route a=data if data else {}, # dict of SADs ) serder = serdering.SerderKERI(sad=sad, makify=True) serder._verify() # raises error if fails verifications return serder
#_, sad = coring.Saider.saidify(sad=sad) #return Serder(ked=sad) # return serialized Self-Addressed Data (SAD)
[docs] def messagize(serder, *, sigers=None, seal=None, wigers=None, cigars=None, pipelined=False): """ Attaches indexed signatures from sigers and/or cigars and/or wigers to KERI message data from serder Parameters: serder (SerderKERI): instance containing the event sigers (list): of Siger instances (optional) to create indexed signatures seal (Union[SealEvent, SealLast]): optional if sigers and If SealEvent use attachment group code TransIdxSigGroups plus attach triple pre+snu+dig made from (i,s,d) of seal plus ControllerIdxSigs plus attached indexed sigs in sigers Else If SealLast use attachment group code TransLastIdxSigGroups plus attach uniple pre made from (i,) of seal plus ControllerIdxSigs plus attached indexed sigs in sigers Else use ControllerIdxSigs plus attached indexed sigs in sigers wigers (list): optional list of Siger instances of witness index signatures cigars (list): optional list of Cigars instances of non-transferable non indexed signatures from which to form receipt couples. Each cigar.vefer.qb64 is pre of receiptor and cigar.qb64 is signature pipelined (bool), True means prepend pipelining count code to attachemnts False means to not prepend pipelining count code Returns: bytearray KERI event message """ msg = bytearray(serder.raw) # make copy into new bytearray so can be deleted atc = bytearray() # attachment if not (sigers or cigars or wigers): raise ValueError("Missing attached signatures on message = {}." "".format(serder.ked)) if sigers: if isinstance(seal, SealEvent): atc.extend(Counter(CtrDex.TransIdxSigGroups, count=1).qb64b) atc.extend(seal.i.encode("utf-8")) atc.extend(Seqner(snh=seal.s).qb64b) atc.extend(seal.d.encode("utf-8")) elif isinstance(seal, SealLast): atc.extend(Counter(CtrDex.TransLastIdxSigGroups, count=1).qb64b) atc.extend(seal.i.encode("utf-8")) atc.extend(Counter(code=CtrDex.ControllerIdxSigs, count=len(sigers)).qb64b) for siger in sigers: atc.extend(siger.qb64b) if wigers: atc.extend(Counter(code=CtrDex.WitnessIdxSigs, count=len(wigers)).qb64b) for wiger in wigers: if wiger.verfer and wiger.verfer.code not in NonTransDex: raise ValueError("Attempt to use tranferable prefix={} for " "receipt.".format(wiger.verfer.qb64)) atc.extend(wiger.qb64b) if cigars: atc.extend(Counter(code=CtrDex.NonTransReceiptCouples, count=len(cigars)).qb64b) for cigar in cigars: if cigar.verfer.code not in NonTransDex: raise ValueError("Attempt to use tranferable prefix={} for " "receipt.".format(cigar.verfer.qb64)) atc.extend(cigar.verfer.qb64b) atc.extend(cigar.qb64b) if pipelined: if len(atc) % 4: raise ValueError("Invalid attachments size={}, nonintegral" " quadlets.".format(len(atc))) msg.extend(Counter(code=CtrDex.AttachedMaterialQuadlets, count=(len(atc) // 4)).qb64b) msg.extend(atc) return msg
[docs] def proofize(sadtsgs=None, *, sadsigers=None, sadcigars=None, pipelined=False): """ Args: sadsigers (list) sad path signatures from transferable identifier of just sigs sadtsgs (list) sad path signatures from transferable identifier sadcigars (list) sad path signatures from non-transferable identifier pipelined (bool), True means prepend pipelining count code to attachemnts False means to not prepend pipelining count code Returns: bytes of CESR Proof Signature attachments """ atc = bytearray() if sadtsgs is None and sadcigars is None: return atc sadtsgs = [] if sadtsgs is None else sadtsgs sadsigers = [] if sadsigers is None else sadsigers sadcigars = [] if sadcigars is None else sadcigars count = 0 for (pather, sigers) in sadsigers: count += 1 atc.extend(coring.Counter(coring.CtrDex.SadPathSig, count=1).qb64b) atc.extend(pather.qb64b) atc.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs, count=len(sigers)).qb64b) for siger in sigers: atc.extend(siger.qb64b) for (pather, prefixer, seqner, saider, sigers) in sadtsgs: count += 1 atc.extend(coring.Counter(coring.CtrDex.SadPathSig, count=1).qb64b) atc.extend(pather.qb64b) atc.extend(coring.Counter(coring.CtrDex.TransIdxSigGroups, count=1).qb64b) atc.extend(prefixer.qb64b) atc.extend(seqner.qb64b) atc.extend(saider.qb64b) atc.extend(coring.Counter(code=coring.CtrDex.ControllerIdxSigs, count=len(sigers)).qb64b) for siger in sigers: atc.extend(siger.qb64b) for (pather, cigars) in sadcigars: count += 1 atc.extend(coring.Counter(coring.CtrDex.SadPathSig, count=1).qb64b) atc.extend(pather.qb64b) atc.extend(coring.Counter(code=coring.CtrDex.NonTransReceiptCouples, count=len(sadcigars)).qb64b) for cigar in cigars: if cigar.verfer.code not in coring.NonTransDex: raise ValueError("Attempt to use tranferable prefix={} for " "receipt.".format(cigar.verfer.qb64)) atc.extend(cigar.verfer.qb64b) atc.extend(cigar.qb64b) msg = bytearray() if pipelined: if len(atc) % 4: raise ValueError("Invalid attachments size={}, nonintegral" " quadlets.".format(len(atc))) msg.extend(coring.Counter(code=coring.CtrDex.AttachedMaterialQuadlets, count=(len(atc) // 4)).qb64b) if count > 1: root = coring.Pather(bext="-") msg.extend(coring.Counter(code=coring.CtrDex.SadPathSigGroup, count=count).qb64b) msg.extend(root.qb64b) msg.extend(atc) return msg
[docs] class Kever: """ Kever is KERI key event verifier class Only supports current version VERSION Has the following public attributes and properties: Class Attributes: EstOnly (bool): True means allow only establishment events False means allow all events DoNotDelegate (bool): True means do not allow delegation other identifiers False means allow delegation of delegated identifiers Attributes: db (Baser | None): instance that manages the LMDB database when provided. When None provided then create and assign vacuous instance of Baser. cues (deque | None): Injected Kevery.cues when provided. Default None. prefixes (list | None): Injected from Kevery when provided. qb64 identifier prefixes of own habitat identifiers. Assign db.prefixes when None When empty operate in promiscuous mode local (bool): Injected from kevery when provided. True means only process msgs for own events when .prefixes is not empty False means only process msgs for not own events when .prefixes is not empty Default is False. version (Versionage): serder.version instance of current event state version prefixer (Prefixer): instance for current event state sner (Number): instance of sequence number fner (Number): instance of first seen ordinal number dater (Dater): instance of first seen datetime serder (SerderKERI): instance of current event with .serder.diger for digest ilk (str): from Ilks for current event type tholder (Tholder): instance for event signing threshold verfers (list): of Verfer instances for current event state set of signing keys ndigers (list): of Diger instances for current event state set of next (rotation) key digests ntholder (Tholder): instance for next (rotation) threshold from serder.ntholder toader (Number): instance of TOAD (threshold of accountable duplicity) wits (list): of qualified qb64 aids for witnesses cuts (list): of qualified qb64 aids for witnesses cut from prev wits list adds (list) of qualified qb64 aids for witnesses added to prev wits list estOnly (bool): config trait True means only allow establishment events Default False. Corresponds to config trait string "EO" doNotDelegate (bool): config trait True means do not allow delegation Default False. Corresponds to config trait string "DND" lastEst (LastEstLoc): namedtuple of int sn .s and qb64 digest .d of last est event delegated (bool): True means delegated identifier, False not delegated delgator (str): qb64 of delegator's prefix Properties: sn (int): sequence number property that returns .sner.num fn (int): first seen ordinal number property the returns .fner.num ndigs (list): of digests qb64 of .digers kevers (dict): reference to self.db.kevers transferable (bool): True if .digers is not empty and pre is transferable ToDo: Add Registrar Backer support: Class variable, instance variable and parse support config trait. raise error for now """ EstOnly = False DoNotDelegate = False
[docs] def __init__(self, *, state=None, serder=None, sigers=None, wigers=None, db=None, estOnly=None, delseqner=None, delsaider=None, firner=None, dater=None, cues=None, prefixes=None, local=False, check=False): """ Create incepting kever and state from inception serder Verify incepting serder against sigers raises ValidationError if not Parameters: state (KeyStateRecord | None): instance for key state notice serder (SerderKERI | None): instance of inception event sigers (list | None): of Siger instances of indexed controller signatures of event. Index is offset into keys list from latest est event wigers (list | None): of Siger instances of indexed witness signatures of event. Index is offset into wits list from latest est event db (Baser | None): instance of lmdb database estOnly (bool | None): True means establishment only events allowed 'EO'. False all events allowed. delseqner (Seqner | None): instance of delegating event sequence number. If this event is not delegated then seqner is ignored delsaider (Saider | None): instance of of delegating event SAID. If this event is not delegated then saider is ignored firner (Seqner | None): instance optional of cloned first seen ordinal If cloned mode then firner maybe provided (not None) When firner provided then compare fn of dater and database and first seen if not match then log and add cue notify problem dater (Dater | None): optional instance of cloned replay datetime If cloned mode then dater maybe provided (not None) When dater provided then use dater for first seen datetime cues (Deck | None): reference to Kevery.cues Deck when provided i.e. notices of events or requests to respond to prefixes (list | None): own prefixes for own local habitats. May not include the prefix of this Kever's event when inception has not yet been accepted into KEL Some restrictions if present If empty then effectively in promiscuous mode local (bool): True means only process msgs for own controller's events if .prefixes is not empty. False means only process msgs for not own events if .prefixes is not empty check (bool): True means do not update the database in any non-idempotent way. Useful for reinitializing the Kevers from a persisted KEL without updating non-idempotent first seen .fels and timestamps. """ if not (state or (serder and sigers)): raise ValueError("Missing required arguments. Need state or serder" " and sigers") if db is None: db = basing.Baser(reopen=True) # default name = "main" self.db = db self.cues = cues self.prefixes = prefixes if prefixes is not None else db.prefixes self.local = True if local else False if state: # preload from state self.reload(state) return # may update state as we go because if invalid we fail to finish init self.version = serder.version # version dispatch ? ilk = serder.ilk # serder.ked["t"] if ilk not in (Ilks.icp, Ilks.dip): raise ValidationError("Expected ilk = {} or {} got {} for evt = {}." "".format(Ilks.icp, Ilks.dip, ilk, serder.ked)) self.ilk = ilk labels = DIP_LABELS if ilk == Ilks.dip else ICP_LABELS for k in labels: if k not in serder.ked: raise ValidationError("Missing element = {} from {} event for " "evt = {}.".format(k, ilk, serder.ked)) self.incept(serder=serder) # do major event validation and state setting self.config(serder=serder, estOnly=estOnly) # assign config traits perms # Validates signers, delegation if any, and witnessing when applicable # If does not validate then escrows as needed and raises ValidationError sigers, delegator, wigers = self.valSigsDelWigs(serder=serder, sigers=sigers, verfers=serder.verfers, tholder=self.tholder, wigers=wigers, toader=self.toader, wits=self.wits, delseqner=delseqner, delsaider=delsaider) self.delegator = delegator self.delegated = True if self.delegator else False wits = serder.backs # serder.ked["b"] # .validateSigsDelWigs above ensures thresholds met otherwise raises exception # all validated above so may add to KEL and FEL logs as first seen # returns fn == None if already logged fn log is non idempotent fn, dts = self.logEvent(serder=serder, sigers=sigers, wigers=wigers, wits=wits, first=True if not check else False, seqner=delseqner, saider=delsaider, firner=firner, dater=dater) if fn is not None: # first is non-idempotent for fn check mode fn is None self.fner = Number(num=fn) self.dater = Dater(dts=dts) self.db.states.pin(keys=self.prefixer.qb64, val=self.state())
@property def sn(self): """ Returns: (int): .sner.num """ return self.sner.num @property def fn(self): """ Returns: (int): .fner.num """ return self.fner.num @property def ndigs(self): """ Returns: (list): digs of digers """ return [diger.qb64 for diger in self.ndigers] @property def kevers(self): """ Returns .baser.kevers """ return self.db.kevers @property def transferable(self): """ Property transferable: Returns True if identifier does not have non-transferable derivation code and .nextor is not None False otherwise """ return True if self.ndigers and self.prefixer.transferable else False
[docs] def locallyOwned(self, pre=''): """Returns True if pre is in .prefixes False otherwise. Indicates that provided identifier prefix is controlled by a local controller from .prefixes i.e pre is a locally owned (controlled) AID (identifier prefix) Parameters: pre (str): qb64 identifier prefix """ return pre in self.prefixes
[docs] def locallyWitnessed(self, serder=None): """Returns True if a local controller is a witness of this Kever's KEL of wits in serder of if None then current wits for this Kever. i.e. self is witnessd by locally owned (controlled) AID (identifier prefix) Parameters: serder ( SerderKERI | None): SerderKERI instace if any """ if serder and serder.pre != self.prefixer.qb64: # same KEL as self return False wits = serder.backs if serder is not None else self.wits return (oset(self.prefixes) & oset(wits))
[docs] def reload(self, state): """ Reload Kever attributes (aka its state) from state (KeyStateRecord) Parameters: state (KeyStateRecord | None): instance for key state notice """ self.version = Versionage._make(state.vn) self.prefixer = Prefixer(qb64=state.i) self.sner = Number(numh=state.s) # sequence number Number instance hex str self.fner = Number(numh=state.f) # first seen ordinal Number hex str self.dater = Dater(dts=state.dt) self.ilk = state.et self.tholder = Tholder(sith=state.kt) self.ntholder = Tholder(sith=state.nt) self.verfers = [Verfer(qb64=key) for key in state.k] self.ndigers = [Diger(qb64=dig) for dig in state.n] self.toader = Number(numh=state.bt) # auto converts from hex num self.wits = state.b self.cuts = state.ee.br self.adds = state.ee.ba self.estOnly = False self.doNotDelegate = True if TraitCodex.DoNotDelegate in state.c else False self.estOnly = True if TraitCodex.EstOnly in state.c else False self.lastEst = LastEstLoc(s=int(state.ee.s, 16), d=state.ee.d) self.delegator = state.di if state.di else None self.delegated = True if self.delegator else False if (raw := self.db.getEvt(key=dgKey(pre=self.prefixer.qb64, dig=state.d))) is None: raise MissingEntryError(f"Corresponding event not found for state=" f"{state}.") self.serder = serdering.SerderKERI(raw=bytes(raw))
# May want to do additional checks here
[docs] def incept(self, serder, estOnly=None): """ Verify incept key event message from serder Parameters: serder is SerderKERI instance of inception event estOnly is boolean to indicate establish only events allowed """ ked = serder.ked self.sner = serder.sner if self.sner.positive: raise ValidationError(f"Nonzero sn={self.sner.num} in inception event.") self.verfers = serder.verfers # converts keys to verifiers self.tholder = serder.tholder # Tholder(sith=ked["kt"]) # parse sith into Tholder instance if len(self.verfers) < self.tholder.size: raise ValidationError("Invalid sith = {} for keys = {} for evt = {}." "".format(ked["kt"], [verfer.qb64 for verfer in self.verfers], ked)) self.prefixer = Prefixer(qb64=serder.pre) if not self.prefixer.verify(ked=ked, prefixed=True): # invalid prefix raise ValidationError("Invalid prefix = {} for inception evt = {}." "".format(self.prefixer.qb64, ked)) self.serder = serder # need whole serder for digest agility comparisons ndigs = serder.ndigs # ked["n"] if not self.prefixer.transferable and ndigs: # nxt must be empty for nontrans prefix raise ValidationError("Invalid inception next digest list not empty for " "non-transferable prefix = {} for evt = {}." "".format(self.prefixer.qb64, ked)) self.ndigers = serder.ndigers self.ntholder = serder.ntholder self.cuts = [] # always empty at inception since no prev event self.adds = [] # always empty at inception since no prev event wits = ked["b"] if not self.prefixer.transferable and wits: # wits must be empty for nontrans prefix raise ValidationError("Invalid inception wits not empty for " "non-transferable prefix = {} for evt = {}." "".format(self.prefixer.qb64, ked)) if len(oset(wits)) != len(wits): raise ValidationError("Invalid backers = {}, has duplicates for evt = {}." "".format(wits, ked)) self.wits = wits toader = Number(num=ked["bt"]) # auto converts hex num to int if wits: if toader.num < 1 or toader.num > len(wits): # out of bounds toad raise ValueError(f"Invalid toad = {toader.num} for backers " f"(wits)={wits} for event={ked}.") else: if toader.num != 0: # invalid toad raise ValueError(f"Invalid toad = {toader.num} for backers " "(wits)={wits} for event={ked}.") self.toader = toader data = ked["a"] if not self.prefixer.transferable and data: # data must be empty for nontrans prefix raise ValidationError("Invalid inception data not empty for " "non-transferable prefix = {} for evt = {}." "".format(self.prefixer.qb64, ked)) # need this to recognize recovery events and transferable receipts # last establishment event location self.lastEst = LastEstLoc(s=self.sner.num, d=self.serder.said)
[docs] def config(self, serder, estOnly=None, doNotDelegate=None): """ Process cnfg field for configuration traits """ # assign traits self.estOnly = (True if (estOnly if estOnly is not None else self.EstOnly) else False) # ensure default estOnly is boolean self.doNotDelegate = (True if (doNotDelegate if doNotDelegate is not None else self.DoNotDelegate) else False) # ensure default doNotDelegate is boolean cnfg = serder.traits # serder.ked["c"] # process cnfg for traits if TraitDex.EstOnly in cnfg: self.estOnly = True if TraitDex.DoNotDelegate in cnfg: self.doNotDelegate = True
[docs] def update(self, serder, sigers, wigers=None, delseqner=None, delsaider=None, firner=None, dater=None, check=False): """ Not an inception event. Verify event serder and indexed signatures in sigers and update state Parameters: serder (SerderKERI): instance of event sigers (list): of SigMat instances of indexed signatures of controller signatures of event. Index is offset into keys list from latest est event and when provided ondex is offset into key digest list from prior next est event to latest est event. wigers (list | None): of Siger instances of indexed witness signatures of event. Index is offset into wits list from latest est event delseqner (Seqner | None): instance of delegating event sequence number. If this event is not delegated then seqner is ignored delsaider (Saider | None): instance of of delegating event said. If this event is not delegated then diger is ignored firner (Seqner | None): Seqner instance of cloned first seen ordinal If cloned mode then firner maybe provided (not None) When firner provided then compare fn of dater and database and first seen if not match then log and add cue notify problem dater (Dater | None): Dater instance of cloned replay datetime If cloned mode then dater maybe provided (not None) When dater provided then use dater for first seen datetime check (bool): True means do not update the database in any non-idempotent way. Useful for reinitializing the Kevers from a persisted KEL without updating non-idempotent first seen .fels and timestamps. """ ked = serder.ked if not self.transferable: # not transferable so no further events allowed raise ValidationError("Unexpected event = {} is nontransferable " " or abandoned state.".format(ked)) if serder.pre != self.prefixer.qb64: raise ValidationError("Mismatch event aid prefix = {} expecting" " = {} for evt = {}.".format(serder.pre, self.prefixer.qb64, ked)) sner = serder.sner # Number instance ensures whole number for sequence number ilk = serder.ilk # ked["t"] if ilk in (Ilks.rot, Ilks.drt): # rotation (or delegated rotation) event if self.delegated and ilk != Ilks.drt: raise ValidationError("Attempted non delegated rotation on " "delegated pre = {} with evt = {}." "".format(serder.pre, ked)) tholder, toader, wits, cuts, adds = self.rotate(serder) # Validates signers, delegation if any, and witnessing when applicable # returned sigers and wigers are verified signatures # If does not validate then escrows as needed and raises ValidationError sigers, delegator, wigers = self.valSigsDelWigs(serder=serder, sigers=sigers, verfers=serder.verfers, tholder=tholder, wigers=wigers, toader=toader, wits=wits, delseqner=delseqner, delsaider=delsaider) # rotation so check rotation threshold against exposed sigers versus # prior next digers in .ndigers #ondices = self.exposeds(sigers) #if not self.ntholder.satisfy(indices=ondices): #self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) #if delseqner and delsaider: # save in case not attached later #self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) #raise MissingSignatureError(f"Failure satisfying nsith=" #f"{self.ntholder.sith} on sigs=" #f"{[siger.qb64 for siger in sigers]}" #f" for evt={serder.ked}.") # .validateSigsDelWigs above ensures thresholds met otherwise raises exception # all validated above so may add to KEL and FEL logs as first seen fn, dts = self.logEvent(serder=serder, sigers=sigers, wigers=wigers, wits=wits, first=True if not check else False, seqner=delseqner, saider=delsaider, firner=firner, dater=dater) # nxt and signatures verify so update state self.sner = sner # sequence number Number instance self.serder = serder # need whole serder for digest agility compare self.ilk = ilk self.tholder = tholder self.verfers = serder.verfers self.ndigers = serder.ndigers self.ntholder = serder.ntholder self.toader = toader self.wits = wits self.cuts = cuts self.adds = adds # last establishment event location need this to recognize recovery events self.lastEst = LastEstLoc(s=self.sner.num, d=self.serder.said) if fn is not None: # first is non-idempotent for fn check mode fn is None self.fner = Number(num=fn) self.dater = Dater(dts=dts) self.db.states.pin(keys=self.prefixer.qb64, val=self.state()) elif ilk == Ilks.ixn: # subsequent interaction event if self.estOnly: raise ValidationError("Unexpected non-establishment event = {}." "".format(serder.ked)) for k in IXN_LABELS: if k not in ked: raise ValidationError("Missing element = {} from {} event." " evt = {}.".format(k, Ilks.ixn, ked)) if not sner.num == (self.sner.num + 1): # sn not in order raise ValidationError("Invalid sn = {} expecting = {} for evt " "= {}.".format(sner.num, self.sner.num + 1, ked)) if not self.serder.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)) # interaction event use keys, sith, toad, and wits from pre-existing Kever state # Validates signers, delegation if any, and witnessing when applicable # If does not validate then escrows as needed and raises ValidationError sigers, delegator, wigers = self.valSigsDelWigs(serder=serder, sigers=sigers, verfers=self.verfers, tholder=self.tholder, wigers=wigers, toader=self.toader, wits=self.wits) # .validateSigsDelWigs above ensures thresholds met otherwise raises exception # all validated above so may add to KEL and FEL logs as first seen fn, dts = self.logEvent(serder=serder, sigers=sigers, wigers=wigers, first=True if not check else False) # First seen accepted # validates so update state self.sner = sner # sequence number Number instance self.serder = serder # need for digest agility includes .serder.diger self.ilk = ilk if fn is not None: # first is non-idempotent for fn check mode fn is None self.fner = Number(num=fn) self.dater = Dater(dts=dts) self.db.states.pin(keys=self.prefixer.qb64, val=self.state()) else: # unsupported event ilk so discard raise ValidationError("Unsupported ilk = {} for evt = {}.".format(ilk, ked))
[docs] def rotate(self, serder): """ Generic Rotate Operation Validation Processing Validates provisional rotation Same logic for both 'rot' and 'drt' (plain and delegated rotation) Returns: tuple (tholder, toader, wits, cuts, adds) of provisional results of rotation subject to additional validation Parameters: serder (SerderKERI): instance of rotation ('rot' or 'drt') event. """ ked = serder.ked sner = serder.sner pre = serder.pre # ked["i"] # controller AID prefix prior = serder.prior # ked["p"] # prior event said ilk = serder.ilk if sner.num > self.sner.num + 1: # out of order event raise ValidationError(f"Out of order event sn = {sner.num} expecting" f" = {self.sner.num + 1} for evt = {ked}.") elif sner.num <= self.sner.num: # stale or recovery # stale events could be duplicitous # duplicity detection should have happend in Kevery before .update # and .rotate called so raise exception if stale # seems redundant but protects bare .update if not called by Kevery if ((ilk == Ilks.rot and sner.num <= self.lastEst.s) or (ilk == Ilks.drt and sner.num < self.lastEst.s)): # stale event raise ValidationError("Stale event sn = {} expecting" " = {} for evt = {}.".format(sner.num, self.sner.num + 1, ked)) else: # recovery event rot sn > self.lastEst.s or drt sn = self.lastEst.s if ilk == Ilks.rot and self.ilk != Ilks.ixn: # rot recovery may only override ixn state raise ValidationError("Invalid recovery attempt: Recovery" "at ilk = {} not ilk = {} for evt" " = {}.".format(self.ilk, Ilks.ixn, ked)) psn = sner.num - 1 # use sn of prior event to fetch prior event # fetch raw serialization of last inserted event at psn pdig = self.db.getKeLast(key=snKey(pre=pre, sn=psn)) if pdig is None: raise ValidationError("Invalid recovery attempt: " "Bad sn = {} for event = {}." "".format(psn, ked)) praw = self.db.getEvt(key=dgKey(pre=pre, dig=pdig)) if praw is None: raise ValidationError("Invalid recovery attempt: " " Bad dig = {}.".format(pdig)) pserder = serdering.SerderKERI(raw=bytes(praw)) # deserialize prior event raw if not pserder.compare(said=prior): # bad recovery event raise ValidationError("Invalid recovery attempt:" "Mismatch recovery event prior dig" "= {} with dig = {} of event sn = {}" " evt = {}.".format(prior, pserder.said, psn, ked)) else: # sn == self.sn + 1 new non-recovery event if not self.serder.compare(said=prior): # prior event dig not match raise ValidationError("Mismatch event dig = {} with" " state dig = {} for evt = {}." "".format(prior, self.serder.said, ked)) # check derivation code of pre for non-transferable if not self.ndigers: # prior next list is empty so rotations not allowed raise ValidationError("Attempted rotation for nontransferable" " prefix = {} for evt = {}." "".format(self.prefixer.qb64, ked)) tholder = serder.tholder # Tholder(sith=ked["kt"]) # parse sith into Tholder instance keys = serder.keys # event's keys ked["k"] if len(keys) < tholder.size: raise ValidationError(f"Invalid sith = {serder.tholder} for keys = " f"{keys} for evt = {ked}.") # compute wits from existing .wits with new cuts and adds from event # use ordered set math ops to verify and ensure strict ordering of wits # cuts and add to ensure that indexed signatures on indexed witness # receipts work witset = oset(self.wits) cuts = serder.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 wits raise ValidationError("Invalid cuts = {}, not all members in wits" " for evt = {}.".format(cuts, ked)) adds = serder.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 wits = {} and adds = {} for " "evt = {}.".format(self.wits, adds, ked)) wits = list((witset - cutset) | addset) if len(wits) != (len(self.wits) - len(cuts) + len(adds)): # redundant? raise ValidationError("Invalid member combination among wits = {}, cuts ={}, " "and adds = {} for evt = {}.".format(self.wits, cuts, adds, ked)) toader = serder.bner # Number(num=ked["bt"]) # auto converts hex num to int if wits: if toader.num < 1 or toader.num > len(wits): # out of bounds toad raise ValueError(f"Invalid toad = {toader.num} for backers " f"(wits)={wits} for event={ked}.") else: if toader.num != 0: # invalid toad raise ValueError(f"Invalid toad = {toader.num} for backers " "(wits)={wits} for event={ked}.") return tholder, toader, wits, cuts, adds
[docs] def valSigsDelWigs(self, serder, sigers, verfers, tholder, wigers, toader, wits, delseqner=None, delsaider=None): """ Returns triple (sigers, delegator, wigers) where: sigers is unique validated signature verified members of inputed sigers delegator is qb64 delegator prefix if delegated else None wigers is unique validated signature verified members of inputed wigers Validates sigers signatures by validating indexes, verifying signatures, and validating threshold sith. Validate witness receipts by validating indexes, verifying witness signatures and validating toad. Witness validation is a function of wits .prefixes and .local Parameters: serder (SerderKERI): instance of event sigers (list): of Siger instances of indexed controllers signatures. Index is offset into verfers list from which public key may be derived. verfers (list): of Verfer instances of keys from latest est event tholder (Tholder): instance of sith threshold wigers (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. toader (Number): instance of backer witness threshold wits (list): of qb64 non-transferable prefixes of witnesses used to derive werfers for wigers delseqner (Seqner | None): instance of delegating event sequence number. If this event is not delegated then seqner is ignored delsaider (Saider | None): instance of of delegating event said. If this event is not delegated then saider is ignored """ if len(verfers) < tholder.size: raise ValidationError("Invalid sith = {} for keys = {} for evt = {}." "".format(tholder.sith, [verfer.qb64 for verfer in verfers], serder.ked)) # get unique verified sigers and indices lists from sigers list sigers, indices = verifySigs(raw=serder.raw, sigers=sigers, verfers=verfers) # sigers now have .verfer assigned # check if minimally signed in order to continue processing if not indices: # must have a least one verified sig raise ValidationError("No verified signatures for evt = {}." "".format(serder.ked)) werfers = [Verfer(qb64=wit) for wit in wits] # get witnes signatures # get unique verified wigers and windices lists from wigers list wigers, windices = verifySigs(raw=serder.raw, sigers=wigers, verfers=werfers) # each wiger now has added to it a werfer of its wit in its .verfer property # escrow if not fully signed vs threshold if not tholder.satisfy(indices): # at least one but not enough self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) if delseqner and delsaider: self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) raise MissingSignatureError(f"Failure satisfying sith = {tholder.sith}" f" on sigs for {[siger.qb64 for siger in sigers]}" f" for evt = {serder.ked}.") if serder.ilk in (Ilks.rot, Ilks.drt): # rotation so check prior next threshold # prior next threshold in .ntholder and digers in .ndigers ondices = self.exposeds(sigers) if not self.ntholder.satisfy(indices=ondices): self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) if delseqner and delsaider: # save in case not attached later self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) raise MissingSignatureError(f"Failure satisfying prior nsith=" f"{self.ntholder.sith} with exposed " f"sigs= {[siger.qb64 for siger in sigers]}" f" for new est evt={serder.ked}.") delegator = self.validateDelegation(serder, sigers=sigers, wigers=wigers, delseqner=delseqner, delsaider=delsaider) # Kevery .process event logic does not prevent this from seeing event when # not local and event pre is own pre if not self.locallyOwned(serder.pre): # not in self.prefixes if ((wits and not self.prefixes) or # in promiscuous mode so assume must verify toad (wits and self.prefixes and not self.local and # not promiscuous nonlocal not (oset(self.prefixes) & oset(wits)))): # own prefix is not a witness # validate that event is fully witnessed if wits: if toader.num < 1 or toader.num > len(wits): # out of bounds toad raise ValidationError(f"Invalid toad = {toader.num} for wits = {wits}") else: if toader.num != 0: # invalid toad raise ValidationError(f"Invalid toad = {toader.num} for wits = {wits}") if len(windices) < toader.num: # not fully witnessed yet if self.escrowPWEvent(serder=serder, wigers=wigers, sigers=sigers, seqner=delseqner, saider=delsaider): # cue to query for witness receipts self.cues.push(dict(kin="query", q=dict(pre=serder.pre, sn=serder.snh))) raise MissingWitnessSignatureError(f"Failure satisfying toad={toader.num} " f"on witness sigs=" f"{[siger.qb64 for siger in wigers]} " f"for event={serder.ked}.") return sigers, delegator, wigers
[docs] def exposeds(self, sigers): """Returns list of ondices (indices) suitable for Tholder.satisfy from self.ndigers (prior next key digests ) as exposed by event sigers. Uses dual index feature of siger. Assumes that each siger.verfer is from the correct key given by siger.index and the signature has been verified. A key given by siger.verfer (at siger.index in the current key list) may expose a prior next key hidden by the diger at siger.ondex in .digers. Each returned ondex must be properly exposed by a siger in sigers such that the siger's indexed key given by siger.verfer matches the siger's ondexed digest from digers. The ondexed digest's code is used to compute the digest of the corresponding indexed key verfer to verify that they match. This supports crypto agility for different digest codes, i.e. all digests in .digers may use a different algorithm. Only ondices from properly matching key and digest are returned. Used to extract the indices from the list of prior next digests .digers exposed by the signatures (sigers) on a rotation event of the newly current keys given by each .verfer at .index from sigers. Only checks keys and digests that correspond to provided signatures not all keys and digests defined by the rotation event. Parameters: sigers (list): of Siger instances of indexed signature with .verfer """ odxs = [] for siger in sigers: try: diger = self.ndigers[siger.ondex] except TypeError as ex: # ondex may be None continue except IndexError as ex: continue #raise ValidationError(f'Invalid ondex={siger.ondex} ' #f'to expose digest.') from ex kdig = Diger(ser=siger.verfer.qb64b, code=diger.code).qb64 if kdig == diger.qb64: odxs.append(siger.ondex) return odxs
[docs] def validateDelegation(self, serder, sigers, wigers=None, delseqner=None, delsaider=None): """ Returns delegator's qb64 identifier prefix if validation successful. Rules: If event is not a delegated event then not valid delegation If delegatee's own event (.mine) then valid delegation If delegation seal found in delgator's KEL then valid delegation given valid superseding rules below Otherwise escrow or reject if error condition seal validates with respect to Delegator's KEL Location Seal is from Delegate's establishment event Assumes state setup Parameters: serder (SerderKERI): instance of delegated event serder sigers (list): of Siger instances of indexed controller sigs of delegated event. Assumes sigers is list of unique verified sigs wigers (list | None): of optional Siger instance of indexed witness sigs of delegated event. Assumes wigers is list of unique verified sigs delseqner (Seqner | None): instance of delegating event sequence number. If this event is not delegated then ignored delsaider (Saider | None): instance of of delegating event digest. If this event is not delegated ignored Returns: (str | None): qb64 delegator prefix or None if not delegated Superseding Recovery Supersede means that after an event has already been accepted as first seen into a KEL that a different event with the same sequence number is accepted that supersedes the pre-existing event at that sn. This enables the recovery of events signed by compromised keys. The result of superseded recovery is that the KEL is forked at the sn of the superseding event. All events in the superseded branch of the fork still exist but, by virtue of being superseded, are disputed. The set of superseding events in the superseding fork forms the authoritative branch of the KEL. All the already seen superseded events in the superseded fork still remain in the KEL and may be viewed in order of their original acceptance because the database stores all accepted events in order of acceptance and denotes this order using the first seen ordinal number, fn. The fn is not the same as the sn (sequence number). Each event accepted into a KEL has a unique fn but multiple events due to recovery forks may share the same sn. Superseding Rules for Recovery at given SN (sequence number) A0. Any rotation event may supersede an interaction event at the same sn. (existing rule) A1. A non-delegated rotation may not supersede another rotation at the same sn. (modified rule) A2. An interaction event may not supersede any event. ( existing rule). (B. and C. below provide the new rules) B. A delegated rotation may supersede another delegated rotation at the same sn under either of the following conditions: B1. The superseding rotation's delegating event is later than the superseded rotation's delegating event in the delegator's KEL, i.e. the sn of the superseding event's delegation is higher than the superseded event's delegation. B2. The sn of the superseding rotation's delegating event is the same as the sn of the superseded rotation's delegating event in the delegator's KEL and the superseding rotation's delegating event is a rotation and the superseded rotation's delegating event is an interaction, i.e. the superseding rotation's delegating event is itself a superseding rotation of the superseded rotations delegating interaction event in the delgator's KEL C. IF Neither A nor B is satisfied, then recursively apply rules A. and B. to the delegating events of those delegating events and so on until either A. or B. is satisfied, or the root KEL of the delegation has been reached. C1. If neither A. nor B. is satisfied by recursive application on the delegator's KEL (i.e. the root KEL of the delegation has been reached without satisfaction) then the superseding rotation is discarded. The terminal case of the recursive application will occur at the root KEL which by defintion is non-delegated wherefore either A. or B. must be satisfied, or else the superseding rotation must be discarded. """ if serder.ilk not in (Ilks.dip, Ilks.drt): # not delegated return None # delegator is None # verify delegator and attachment pointing to delegating event if serder.ilk == Ilks.dip: delegator = serder.delpre # delegator from dip event if not delegator: raise ValidationError(f"Empty or missing delegator for delegated" f" inception event = {serder.ked}.") else: # serder.ilk == Ilks.drt so rotation delegator = self.delegator # if we are the delegatee, accept the event without requiring the # delegator validation via an anchored delegation seal or by requiring # it to be witnessed # ToDo XXXX add local lax check after figure out dist multisig group # ToDo XXXX add check for witness to accept so that witness will # add to its KEL without waiting for delegation seal to be anchored in # delegator's KEL witness cue in Kevery will then generate reciept if self.locallyOwned(serder.pre) or self.locallyWitnessed(serder=serder): return delegator # during initial delegation we just escrow the delcept event if delseqner is None and delsaider is None and delegator is not None: self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) raise MissingDelegationError("No delegation seal for delegator {} " "with evt = {}.".format(delegator, serder.ked)) ssn = validateSN(sn=delseqner.snh, inceptive=False) # delseqner Number should already do this #ssn = sner.num sner is Number seqner is Seqner need to replace Seqners with Numbers # get the dig of the delegating event. Using getKeLast ensures delegating # event has not already been superceded key = snKey(pre=delegator, sn=ssn) # database key raw = self.db.getKeLast(key) # get dig of delegating event if raw is None: # no delegating event at key pre, sn # ToDo XXXX create cue to send query to fetch delegating event from # delegator self.cues.push(dict(kin="query", q=dict(pre=delegator, sn=delseqner.snh, dig=delsaider.qb64))) # escrow event here inceptive = True if serder.ilk in (Ilks.icp, Ilks.dip) else False sn = validateSN(sn=serder.snh, inceptive=inceptive) self.escrowPSEvent(serder=serder, sigers=sigers, wigers=wigers) self.escrowPACouple(serder=serder, seqner=delseqner, saider=delsaider) raise MissingDelegationError("No delegating event from {} at {} for " "evt = {}.".format(delegator, delsaider.qb64, serder.ked)) # get the delegating event from dig ddig = bytes(raw) key = dgKey(pre=delegator, dig=ddig) # database key raw = self.db.getEvt(key) if raw is None: # drop event raise ValidationError("Missing delegation from {} at event dig = {} for evt = {}." "".format(delegator, ddig, serder.ked)) dserder = serdering.SerderKERI(raw=bytes(raw)) # delegating event # compare digests to make sure they match here if not dserder.compare(said=delsaider.qb64): # drop event raise ValidationError("Invalid delegation from {} at event dig = {} for evt = {}." "".format(delegator, ddig, serder.ked)) if self.kevers is None or delegator not in self.kevers: # drop event raise ValidationError("Missing Kever for delegator = {} for evt = {}." "".format(delegator, serder.ked)) dkever = self.kevers[delegator] if dkever.doNotDelegate: # drop event raise ValidationError("Delegator = {} for evt = {}," " does not allow delegation.".format(delegator, serder.ked)) found = False # find event seal of delegated event in delegating data # XXXX ToDo need to change logic here to support native CESR seals not just dicts # for JSON, CBOR, MGPK for dseal in dserder.seals: # find delegating seal anchor if tuple(dseal.keys()) == SealEvent._fields: seal = SealEvent(**dseal) if (seal.i == serder.pre and seal.s == serder.sner.numh and serder.compare(said=seal.d)): found = True break if not found: # drop event raise ValidationError("Missing delegation from {} in {} for evt = {}." "".format(delegator, dserder.seals, serder.ked)) # Assumes database is reverified each bootup chain-of-custody of dic broken. # Rule for non-supeding delegated rotation of rotation. # Returning delegator indicates success and eventually results acceptance # via Kever.logEvent which also writes the delgating event source couple to # db.aess so we can find it later if ((serder.ilk == Ilks.dip) or # delegated inception (serder.sner.num == self.sner.num + 1) or # inorder event (serder.sner.num == self.sner.num and self.ilk == Ilks.ixn and serder.ilk == Ilks.drt)): # recovery rotation superseding ixn return delegator # indicates delegation valid with return of delegator # Kever.logEvent saves authorizer (delegator) seal source couple in # db.aess data base so can use it here to recusively look up delegating # events # set up recursive search for superseding delegations serfn = serder # new potentially superseding delegated event i.e. serf new bossn = dserder # new delegating event of superseding delegated event i.e. boss new serfo = self.serder # original delegated event i.e. serf original bosso = self.fetchDelegatingEvent(delegator, serfo) while (True): # superseding delegated rotation of rotation recovery rules # Only get to here if same sn for drt existing and drt superseding if (bossn.sn > bosso.sn or # later supersedes (bossn.Ilk == Ilks.drt and bosso.Ilk == Ilks.ixn) ): # drt supersedes ixn return delegator # valid superseding delegation if bossn.said == bosso.said: # same delegating event nseals = [SealEvent(**seal) for seal in bossn.seals if tuple(seal.keys()) == SealEvent._fields] nindex = nseals.index(SealEvent(i=serfn.pre, s=serfn.snh, d=serfn.said)) oseals = [SealEvent(**seal) for seal in bosso.seals if tuple(seal.keys()) == SealEvent._fields] oindex = oseals.index(SealEvent(i=serfo.pre, s=serfo.snh, d=serfo.said)) if nindex > oindex: # later seal supersedes # assumes index can't be None return delegator # valid superseding delegation else: # ToDo: XXXX may want to cue up business logic for delegator # if self.mine(delegator): # failed attempt at recovery raise ValidationError(f"Invalid delegation recovery rotation" f"of {serfo.ked} by {serfn.ked}") # tie condition same sn and drt so need to climb delegation chain serfn = bossn bossn = self.fetchDelegatingEvent(delegator, serfn) serfo = bosso bosso = self.fetchDelegatingEvent(delegator, serfo)
# repeat
[docs] def fetchDelegatingEvent(self, delegator, serder): """Returns delegating event by delegator of delegated event given by serder otherwise raises ValidationError. Assumes serder is already delegated event Parameters: delegator (str): qb64 of identifier prefix of delegator serder (SerderKERI): delegated serder """ dgkey = dgKey(pre=serder.preb, dig=serder.saidb) # database key if (couple := self.db.getAes(dgkey)): # delegation source couple seqner, saider = deSourceCouple(couple) dgkey = dgKey(pre=delegator, dig=saider) # event at its said # get event by dig not by sn at last event because may have been superceded if not (raw := self.db.getEvt(dgkey)): # database broken this should never happen so do not supersede raise ValidationError(f"Missing delegation event for {serder.ked}") dserder = serdering.SerderKERI(raw=bytes(raw)) # original delegating event i.e. boss original else: #try to find seal the hard way seal = SealEvent(i=serder.pre, s=serder.snh, d=serder.said)._asdict if not (dserder := self.db.findAnchoringSealEvent(pre=serder.delpre, seal=seal)): # database broken this should never happen so do not supersede raise ValidationError(f"Missing delegation source seal for {serder.ked}") return dserder
[docs] def logEvent(self, serder, sigers=None, wigers=None, wits=None, first=False, seqner=None, saider=None, firner=None, dater=None): """ Update associated logs for verified event. Update is idempotent. Logs will not write dup at key if already exists. Parameters: serder is SerderKERI instance of current event sigers is optional list of Siger instance for current event wigers is optional list of Siger instance of indexed witness sigs wits is optional list of current witnesses provide during any establishment event first is Boolean True means first seen accepted log of event. Otherwise means idempotent log of event to accept additional signatures beyond the threshold provided for first seen seqner is Seqner instance of delegating event sequence number. If this event is not delegated then seqner is ignored saider is Saider instance of of delegating event said. If this event is not delegated then diger is ignored firner is optional Seqner instance of cloned first seen ordinal If cloned mode then firner maybe provided (not None) When firner provided then compare fn of dater and database and first seen if not match then log and add cue notify problem dater is optional Dater instance of cloned replay datetime If cloned mode then dater maybe provided (not None) When dater provided then use dater for first seen datetime """ fn = None # None means not a first seen log event so does not return an fn dgkey = dgKey(serder.preb, serder.saidb) dtsb = helping.nowIso8601().encode("utf-8") self.db.putDts(dgkey, dtsb) # idempotent do not change dts if already if sigers: self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) # idempotent if wigers: self.db.putWigs(dgkey, [siger.qb64b for siger in wigers]) if wits: self.db.wits.put(keys=dgkey, vals=[coring.Prefixer(qb64=w) for w in wits]) self.db.putEvt(dgkey, serder.raw) # idempotent (maybe already excrowed) val = (coring.Prefixer(qb64b=serder.preb), coring.Seqner(sn=serder.sn)) for verfer in (serder.verfers if serder.verfers is not None else []): self.db.pubs.add(keys=(verfer.qb64,), val=val) for diger in (serder.ndigers if serder.ndigers is not None else []): self.db.digs.add(keys=(diger.qb64,), val=val) if first: # append event dig to first seen database in order if seqner and saider: # delegation for authorized delegated or issued event couple = seqner.qb64b + saider.qb64b self.db.setAes(dgkey, couple) # authorizer (delegator/issuer) event seal fn = self.db.appendFe(serder.preb, serder.saidb) if firner and fn != firner.sn: # cloned replay but replay fn not match if self.cues is not None: # cue to notice BadCloneFN self.cues.push(dict(kin="noticeBadCloneFN", serder=serder, fn=fn, firner=firner, dater=dater)) logger.info("Kever Mismatch Cloned Replay FN: %s First seen " "ordinal fn %s and clone fn %s \nEvent=\n%s\n", serder.preb, fn, firner.sn, serder.pretty()) if dater: # cloned replay use original's dts from dater dtsb = dater.dtsb self.db.setDts(dgkey, dtsb) # first seen so set dts to now self.db.fons.pin(keys=dgkey, val=Seqner(sn=fn)) logger.info("Kever state: %s First seen ordinal %s at %s\nEvent=\n%s\n", serder.preb, fn, dtsb.decode("utf-8"), serder.pretty()) self.db.addKe(snKey(serder.preb, serder.sn), serder.saidb) logger.info("Kever state: %s Added to KEL valid event=\n%s\n", serder.preb, serder.pretty()) return (fn, dtsb.decode("utf-8")) # (fn int, dts str) if first else (None, dts str)
[docs] def escrowPSEvent(self, serder, sigers, wigers=None): """ Update associated logs for escrow of partially signed event or fully signed delegated event but not yet verified delegation. Parameters: serder is SerderKERI instance of event sigers is list of Siger instances of indexed controller sigs wigers is optional list of Siger instance of indexed witness sigs """ dgkey = dgKey(serder.preb, serder.saidb) self.db.putDts(dgkey, helping.nowIso8601().encode("utf-8")) # idempotent self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) if wigers: self.db.putWigs(dgkey, [siger.qb64b for siger in wigers]) self.db.putEvt(dgkey, serder.raw) snkey = snKey(serder.preb, serder.sn) self.db.addPse(snkey, serder.saidb) # b'EOWwyMU3XA7RtWdelFt-6waurOTH_aW_Z9VTaU-CshGk.00000000000000000000000000000001' logger.info("Kever state: Escrowed partially signed or delegated " "event = %s\n", serder.ked)
[docs] def escrowPACouple(self, serder, seqner, saider): """ Update associated logs for escrow of partially authenticated issued event. Assuming signatures are provided elsewhere. Partial authentication results from either a partially signed event or a fully signed delegated event but whose delegation is not yet verified. Escrow allows escrow processor to retrieve serder from key and source couple from val in order to to re-verify authentication status. Sigs are escrowed elsewhere. Parameters: serder is SerderKERI instance of delegated or issued event seqner is Seqner instance of sn of seal source event of delegator/issuer saider is Saider instance of said of delegator/issuer """ dgkey = dgKey(serder.preb, serder.saidb) couple = seqner.qb64b + saider.qb64b self.db.putPde(dgkey, couple) # idempotent logger.info("Kever state: Escrowed source couple for partially signed " "or delegated event = %s\n", serder.ked)
[docs] def escrowPWEvent(self, serder, wigers, sigers=None, seqner=None, saider=None): """ Update associated logs for escrow of partially witnessed event Parameters: serder is SerderKERI instance of event wigers is list of Siger instance of indexed witness sigs sigers is optional list of Siger instances of indexed controller sigs seqner is Seqner instance of sn of seal source event of delegator/issuer saider is Diger instance of digest of delegator/issuer """ dgkey = dgKey(serder.preb, serder.saidb) self.db.putDts(dgkey, helping.nowIso8601().encode("utf-8")) # idempotent if wigers: self.db.putWigs(dgkey, [siger.qb64b for siger in wigers]) if sigers: self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) if seqner and saider: couple = seqner.qb64b + saider.qb64b self.db.putPde(dgkey, couple) self.db.putEvt(dgkey, serder.raw) logger.info("Kever state: Escrowed partially witnessed " "event = %s\n", serder.ked) return self.db.addPwe(snKey(serder.preb, serder.sn), serder.saidb)
[docs] def state(self): """ Returns KeyStateRecord instance of current key state """ eevt = StateEstEvent(s="{:x}".format(self.lastEst.s), d=self.lastEst.d, br=self.cuts, ba=self.adds) cnfg = [] if self.estOnly: cnfg.append(TraitDex.EstOnly) if self.doNotDelegate: cnfg.append(TraitDex.DoNotDelegate) return (state(pre=self.prefixer.qb64, sn=self.sn, # property self.sner.num pig=(self.serder.prior if self.serder.prior is not None else ""), dig=self.serder.said, fn=self.fn, # property self.fner.num stamp=self.dater.dts, # need to add dater object for first seen dts eilk=self.ilk, keys=[verfer.qb64 for verfer in self.verfers], eevt=eevt, sith=self.tholder.sith, nsith=self.ntholder.sith if self.ntholder else '0', ndigs=[diger.qb64 for diger in self.ndigers], toad=self.toader.num, wits=self.wits, cnfg=cnfg, dpre=self.delegator, ) )
[docs] def fetchPriorDigers(self, sn: int | None = None) -> list | None: """ Returns either the most recent prior list of digers before .lastEst or None Starts searching at sn or if sn is None at sn = .lastEst.s - 1 Returns list of Digers instances at the most recent prior est event relative to the given sequence number (sn) otherwise returns None. Walks backwards to the more recent prior establishment event before the .sn if any. If sn represents an interaction event (ixn) then the result will be the current valid list of digers. If sn represents an establishment event then the result will be the list of digers immediately prior to the current list. Parameters: sn (int | None): sn to start searching. If None then start at .lastEst.s - 1 Returns: digers (list | None): of Diger instances or None if no prior est evt to current .lastEst """ pre = self.prefixer.qb64 if sn is None: sn = self.lastEst.s - 1 for digb in self.db.getKelBackIter(pre, sn): dgkey = dgKey(pre, digb) raw = self.db.getEvt(dgkey) serder = serdering.SerderKERI(raw=bytes(raw)) if serder.estive: # establishment event return serder.ndigers return None
[docs] def fetchLatestContribTo(self, verfers, sn: int | None = None): """ Returns tuple of (sn, index, verfer) from latest est event whose verfer is found in verfers at index offset else None if not found. Fetches latest event sn and associated index and verfer that contributed to the provided verfers at index. Starts searching at sn or if sn is None at sn = .lastEst.s Returns tuple (sn, index, verfer) from the latest est event that matches by starting at the given sequence number (sn) and walking backwards otherwise returns None. If given sn represents an interaction event (ixn) then a latest possible matching result may be from an event that is no later than the last est event prior to that interaction event. If the sn represents an establishment event then the latest possible matching result may be from that event. Parameters: verfers (list[Verfer]): of verfer instances sn (int | None): sn to start searching. If None then start at .lastEst.s Returns: tuple(int, int,Verfer) | None: where tuple is of form (sn, idx, verfer). sn is sequence number. idx is index of verfer in verfers verfer is instance of Verfer """ pre = self.prefixer.qb64 if sn is None: sn = self.lastEst.s keys = [verfer.qb64 for verfer in verfers] for digb in self.db.getKelBackIter(pre, sn): dgkey = dgKey(pre, digb) raw = self.db.getEvt(dgkey) serder = serdering.SerderKERI(raw=bytes(raw)) if serder.estive: # establishment event key = serder.verfers[0].qb64 try: i = keys.index(key) # find index of key in keys except ValueError: # not found continue return (serder.sn, i, serder.verfers[0]) return None
[docs] def fetchLatestContribFrom(self, verfer, sn: int | None = None): """ Returns tuple of form (sn, index, verfers) where verfers is a list of verfers from latest est event where verfer is found in that event's verfers at index offset else None if not found. Fetches latest event sn and associated verfers list that recieved a contribution from the provided verfer at index. Starts searching at sn or if sn is None at sn = .lastEst.s Returns tuple (sn, index, list[verfers]) from the latest est event that matches by starting at the given sequence number (sn) and walking backwards otherwise returns None. If given sn represents an interaction event (ixn) then a latest possible matching result may be from an event that is no later than the last est event prior to that interaction event. If the sn represents an establishment event then the latest possible matching result may be from that event. Parameters: verfer (Verfer): instance of verfer sn (int | None): sn to start searching. If None then start at .lastEst.s Returns: tuple(int, int, list[Verfer]) | None: where tuple is of form (sn, index, verfers) sn is sequence number index is index into verfers of verfers verfers is list of Verfer instances. """ pre = self.prefixer.qb64 if sn is None: sn = self.lastEst.s key = verfer.qb64 for digb in self.db.getKelBackIter(pre, sn): dgkey = dgKey(pre, digb) raw = self.db.getEvt(dgkey) serder = serdering.SerderKERI(raw=bytes(raw)) if serder.estive: # establishment event keys = [verfer.qb64 for verfer in serder.verfers] try: i = keys.index(key) # find index of key in keys except ValueError: # not found continue return (serder.sn, i, serder.verfers) return None
[docs] class Kevery: """ Kevery (Key Event Message Processing Facility) processes an incoming message stream composed of KERI key event related messages and attachments. Kevery acts a Kever (key event verifier) factory for managing key state of KERI identifier prefixes. Only supports current version VERSION Has the following public attributes and properties: Attributes: cues (Deck): of Cues i.e. notices of events needing receipt or requests needing response .db is instance of LMDB Baser object .framed is Boolean stream is packet framed If True Else not framed .pipeline is Boolean, True means use pipeline processor to process ims msgs when stream includes pipelined count codes. lax (bool): True means operate in promiscuous (unrestricted) mode, False means operate in nonpromiscuous (restricted) mode as determined by local and prefixes local (bool): True means only process msgs for own events if not lax False means only process msgs for not own events if not lax cloned (bool): True means cloned message stream so use attached datetimes from clone source not own. False means use current datetime direct (bool): True means direct mode so cue notices for receipts etc False means indirect mode so don't cue notices check (bool): True means do not update the database in any non-idempotent way. Useful for reinitializing the Kevers from a persisted KEL without updating non-idempotent first seen .fels and timestamps. Properties: .kevers is dict of db kevers indexed by pre (qb64) of each Kever .prefixes is OrderedSet of fully qualified base64 identifier prefixes of db local habitats if any. """ TimeoutOOE = 1200 # seconds to timeout out of order escrows TimeoutPSE = 3600 # seconds to timeout partially signed or delegated escrows TimeoutPWE = 3600 # seconds to timeout partially witnessed escrows TimeoutLDE = 3600 # seconds to timeout likely duplicitous escrows TimeoutUWE = 3600 # seconds to timeout unverified receipt escrows TimeoutURE = 3600 # seconds to timeout unverified receipt escrows TimeoutVRE = 3600 # seconds to timeout unverified transferable receipt escrows TimeoutKSN = 3600 # seconds to timeout key state notice message escrows TimeoutQNF = 300 # seconds to timeout query not found escrows
[docs] def __init__(self, *, cues=None, db=None, rvy=None, lax=True, local=False, cloned=False, direct=True, check=False): """ Initialize instance: Parameters: cues (Deck) notices to create responses to evts kevers is dict of Kever instances of key state in db db (Baser): instance of database lax (bool): True means operate in promiscuous (unrestricted) mode, False means operate in nonpromiscuous (restricted) mode as determined by local and prefixes local (bool): True means only process msgs for own events if not lax False means only process msgs for not own events if not lax cloned (bool): True means cloned message stream so use attached datetimes from clone source not own. False means use current datetime direct (bool): True means direct mode so cue notices for receipts etc False means indirect mode so don't cue notices check (bool): True means do not update the database in any non-idempotent way. Useful for reinitializing the Kevers from a persisted KEL without updating non-idempotent first seen .fels and timestamps. """ self.cues = cues if cues is not None else decking.Deck() # subclass of deque if db is None: db = basing.Baser(reopen=True) # default name = "main" self.db = db self.rvy = rvy self.lax = True if lax else False # promiscuous mode self.local = True if local else False # local vs nonlocal restrictions self.cloned = True if cloned else False # process as cloned self.direct = True if direct else False # process as direct mode self.check = True if check else False # process as check mode
@property def kevers(self): """ Returns .db.kevers """ return self.db.kevers @property def prefixes(self): """ Returns .db.prefixes """ return self.db.prefixes
[docs] def fetchWitnessState(self, pre, sn): """ Returns the list of witness for the identifier prefix at the sequence number Returns the witness state (list of witnesses) at the given sequence number (sn) of the identifier prefix (pre). It uses the .wits database that stores witness state at the sequence number of each establishment event. If sn represents an interaction event (ixn) it searches backwards for the last establishment event prior to sn and returns that witness state. Args: pre (str): identifier prefix qb64 sn (int): sequence number of the event for which witness state is desired Returns: list: list of coring.Prefixer objects representing the witness state for the identifier prefix at the sequence number """ preb = pre.encode("utf-8") for digb in self.db.getKelBackIter(preb, sn): dgkey = dgKey(preb, digb) raw = self.db.getEvt(dgkey) serder = serdering.SerderKERI(raw=bytes(raw)) if serder.estive: wits = self.db.wits.get(dgkey) return wits return []
[docs] def processEvent(self, serder, sigers, *, wigers=None, delseqner=None, delsaider=None, firner=None, dater=None): """ Process one event serder with attached indexd signatures sigers Parameters: serder is SerderKERI instance of event to process sigers is list of Siger instances of attached controller indexed sigs wigers is optional list of Siger instances of attached witness indexed sigs delseqner is Seqner instance of delegating event sequence number. If this event is not delegated then seqner is ignored delsaider is Saider instance of of delegating event SAID. If this event is not delegated then saider is ignored firner is optional Seqner instance of cloned first seen ordinal If cloned mode then firner maybe provided (not None) When firner provided then compare fn of dater and database and first seen if not match then log and add cue notify problem dater is optional Dater instance of cloned replay datetime If cloned mode then dater maybe provided (not None) When dater provided then use dater for first seen datetime """ # fetch ked ilk pre, sn, dig to see how to process pre = serder.pre ked = serder.ked # See todo for Prefixer fix redundancy XXX try: # see if code of pre is supported and matches size of pre Prefixer(qb64=pre) except Exception as ex: # if unsupported code or bad size raises error raise ValidationError("Invalid pre = {} for evt = {}." "".format(pre, ked)) from ex sn = serder.sn ilk = serder.ilk # ked["t"] said = serder.said if pre not in self.kevers: # first seen event for pre if ilk in (Ilks.icp, Ilks.dip): # first seen and inception so verify event keys # kever init verifies basic inception stuff and signatures # raises exception if problem # otherwise adds to KEL # create kever from serder kever = Kever(serder=serder, sigers=sigers, wigers=wigers, db=self.db, delseqner=delseqner, delsaider=delsaider, firner=firner if self.cloned else None, dater=dater if self.cloned else None, cues=self.cues, prefixes=self.prefixes, local=self.local, check=self.check) self.kevers[pre] = kever # not exception so add to kevers if self.direct or self.lax or pre not in self.prefixes: # not own event when owned # create cue for receipt controller or watcher # receipt of actual type is dependent on own type of identifier self.cues.push(dict(kin="receipt", serder=serder)) elif not self.direct: # notice of new event self.cues.push(dict(kin="notice", serder=serder)) if kever.locallyWitnessed(): # ToDo XXXX need to cue task here kin = "witness" self.cues.push(dict(kin="witness", serder=serder)) if kever.locallyOwned(kever.delegator): # delegator may be None # ToDo XXXX need to cue task here to approve delegation by generating # and anchoring SealEvent of serder in delegators KEL # may include MFA business logic for the delegator i.e. is local self.cues.push(dict(kin="approveDelegation", delegator=kever.delegator, serder=serder)) else: # not inception so can't verify sigs etc, add to out-of-order escrow self.escrowOOEvent(serder=serder, sigers=sigers, seqner=delseqner, saider=delsaider, wigers=wigers) raise OutOfOrderError("Out-of-order event={}.".format(ked)) else: # already accepted inception event for pre so already first seen if ilk in (Ilks.icp, Ilks.dip): # another inception event so maybe duplicitous if sn != 0: raise ValueError("Invalid sn={} for inception event={}." "".format(sn, serder.ked)) # check if duplicate of existing inception event since est is icp eserder = self.fetchEstEvent(pre, sn) # latest est evt wrt sn if eserder.said == said: # event is a duplicate but not duplicitous # may have attached valid signature not yet logged # raises ValidationError if no valid sig kever = self.kevers[pre] # get key state # get unique verified lists of sigers and indices from sigers sigers, indices = verifySigs(raw=serder.raw, sigers=sigers, verfers=eserder.verfers) wigers, windices = verifySigs(raw=serder.raw, sigers=wigers, verfers=eserder.berfers) if sigers or wigers: # at least one verified sig or wig so log evt # this allows late arriving witness receipts or controller # signatures to be added to the databse # not first seen inception so ignore return kever.logEvent(serder, sigers=sigers, wigers=wigers) # idempotent update db logs else: # escrow likely duplicitous event self.escrowLDEvent(serder=serder, sigers=sigers) raise LikelyDuplicitousError("Likely Duplicitous event={}.".format(ked)) else: # rot, drt, or ixn, so sn matters kever = self.kevers[pre] # get existing kever for pre sno = kever.sner.num + 1 # proper sn of new inorder event if sn > sno: # sn later than sno so out of order escrow # escrow out-of-order event self.escrowOOEvent(serder=serder, sigers=sigers, seqner=delseqner, saider=delsaider, wigers=wigers) raise OutOfOrderError("Out-of-order event={}.".format(ked)) elif ((sn == sno) or # inorder event (ixn, rot, drt) or (ilk == Ilks.rot and # superseding recovery rot or kever.lastEst.s < sn <= sno) or (ilk == Ilks.drt and # delegated superseding recovery drt kever.lastEst.s <= sn <= sno)): # verify signatures etc and update state if valid # raise exception if problem. # Otherwise adds to KELs kever.update(serder=serder, sigers=sigers, wigers=wigers, delseqner=delseqner, delsaider=delsaider, firner=firner if self.cloned else None, dater=dater if self.cloned else None, check=self.check) if self.direct or self.lax or pre not in self.prefixes: # not own event when owned # create cue for receipt controller or watcher # receipt of actual type is dependent on own type of identifier self.cues.push(dict(kin="receipt", serder=serder)) elif not self.direct: # notice of new event self.cues.push(dict(kin="notice", serder=serder)) if kever.locallyWitnessed(): # ToDo XXXX need to cue task here kin = "witness" self.cues.push(dict(kin="witness", serder=serder)) if kever.locallyOwned(kever.delegator): # delegator may be None # ToDo XXXX need to cue task here to approve delegation by generating # and anchoring SealEvent of serder in delegators KEL # may include MFA business logic for the delegator i.e. is local self.cues.push(dict(kin="approveDelegation", delegator=kever.delegator, serder=serder)) else: # maybe duplicitous # check if duplicate of existing valid accepted event ddig = bytes(self.db.getKeLast(key=snKey(pre, sn))).decode("utf-8") if ddig == said: # event is a duplicate but not duplicitous eserder = self.fetchEstEvent(pre, sn) # latest est event wrt sn # may have attached valid signature not yet logged # raises ValidationError if no valid sig kever = self.kevers[pre] # get unique verified lists of sigers and indices from sigers sigers, indices = verifySigs(raw=serder.raw, sigers=sigers, verfers=eserder.verfers) wits = [wit.qb64 for wit in self.fetchWitnessState(pre, sn)] werfers = [Verfer(qb64=wit) for wit in wits] wigers, windices = verifySigs(raw=serder.raw, sigers=wigers, verfers=werfers) if sigers or wigers: # at least one verified sig or wig so log evt # not first seen update so ignore return kever.logEvent(serder, sigers=sigers, wigers=wigers) # idempotent update db logs else: # escrow likely duplicitous event self.escrowLDEvent(serder=serder, sigers=sigers) raise LikelyDuplicitousError("Likely Duplicitous event={}.".format(ked))
[docs] def processReceiptWitness(self, serder, wigers): """ Process one witness receipt serder with attached witness sigers Parameters: serder is SerderKERI instance of serialized receipt message not receipted event sigers is list of Siger instances that with witness indexed signatures signature in .raw. Index is offset into witness list of latest establishment event for receipted event. Signature uses key pair derived from nontrans witness prefix in associated witness list. Receipt dict labels vs # version string pre # qb64 prefix sn # hex string sequence number ilk # rct dig # qb64 digest of receipted event """ # fetch pre dig to process ked = serder.ked pre = serder.pre sn = serder.sn # Only accept receipt if for last seen version of event at sn snkey = snKey(pre=pre, sn=sn) ldig = self.db.getKeLast(key=snkey) # retrieve dig of last event at sn. if ldig is not None: # verify digs match ldig = bytes(ldig).decode("utf-8") # retrieve event by dig assumes if ldig is not None that event exists at ldig dgkey = dgKey(pre=pre, dig=ldig) raw = bytes(self.db.getEvt(key=dgkey)) # retrieve receipted event at dig # assumes db ensures that raw must not be none lserder = serdering.SerderKERI(raw=raw) # deserialize event raw if not lserder.compare(said=ked["d"]): # stale receipt at sn discard raise ValidationError("Stale receipt at sn = {} for rct = {}." "".format(ked["s"], ked)) # process each couple verify sig and write to db wits = [wit.qb64 for wit in self.fetchWitnessState(pre, sn)] for wiger in wigers: # assign verfers from witness list if wiger.index >= len(wits): continue # skip invalid witness index wiger.verfer = Verfer(qb64=wits[wiger.index]) # assign verfer if wiger.verfer.transferable: # skip transferable verfers continue # skip invalid witness prefix if not self.lax and wiger.verfer.qb64 in self.prefixes: # own is receiptor if pre in self.prefixes: # skip own receiptor of own event # sign own events not receipt them logger.info("Kevery process: skipped own receipt attachment" " on own event receipt=\n%s\n", serder.pretty()) continue # skip own receipt attachment on own event if not self.local: # own receipt on other event when not local logger.info("Kevery process: skipped own receipt attachment" " on nonlocal event receipt=\n%s\n", serder.pretty()) continue # skip own receipt attachment on non-local event if wiger.verfer.verify(wiger.raw, lserder.raw): # write receipt indexed sig to database self.db.addWig(key=dgkey, val=wiger.qb64b) else: # no events to be receipted yet at that sn so escrow # get digest from receipt message not receipted event self.escrowUWReceipt(serder=serder, wigers=wigers, said=ked["d"]) raise UnverifiedWitnessReceiptError("Unverified witness receipt={}." "".format(ked))
[docs] def processReceipt(self, serder, cigars): """ Process one receipt serder with attached cigars Parameters: serder is SerderKERI instance of serialized receipt message not receipted message cigars is list of Cigar instances that contain receipt couple signature in .raw and public key in .verfer Receipt dict labels vs # version string pre # qb64 prefix sn # hex string sequence number ilk # rct dig # qb64 digest of receipted event """ # fetch pre dig to process ked = serder.ked pre = serder.pre sn = serder.sn # Only accept receipt if for last seen version of event at sn snkey = snKey(pre=pre, sn=sn) ldig = self.db.getKeLast(key=snkey) # retrieve dig of last event at sn. if ldig is not None: # verify digs match ldig = bytes(ldig).decode("utf-8") # retrieve event by dig assumes if ldig is not None that event exists at ldig dgkey = dgKey(pre=pre, dig=ldig) raw = bytes(self.db.getEvt(key=dgkey)) # retrieve receipted event at dig # assumes db ensures that raw must not be none lserder = serdering.SerderKERI(raw=raw) # deserialize event raw if not lserder.compare(said=ked["d"]): # stale receipt at sn discard raise ValidationError("Stale receipt at sn = {} for rct = {}." "".format(ked["s"], ked)) # process each couple verify sig and write to db for cigar in cigars: if cigar.verfer.transferable: # skip transferable verfers continue # skip invalid couplets if not self.lax and cigar.verfer.qb64 in self.prefixes: # own is receiptor if pre in self.prefixes: # skip own receipter of own event # sign own events not receipt them logger.info("Kevery process: skipped own receipt attachment" " on own event receipt=\n%s\n", serder.pretty()) continue # skip own receipt attachment on own event if not self.local: # own receipt on other event when not local logger.info("Kevery process: skipped own receipt attachment" " on nonlocal event receipt=\n%s\n", serder.pretty()) continue # skip own receipt attachment on non-local event if cigar.verfer.verify(cigar.raw, lserder.raw): wits = [wit.qb64 for wit in self.fetchWitnessState(pre, sn)] rpre = cigar.verfer.qb64 # prefix of receiptor if rpre in wits: # its a witness receipt index = wits.index(rpre) # create witness indexed signature wiger = Siger(raw=cigar.raw, index=index, verfer=cigar.verfer) self.db.addWig(key=dgkey, val=wiger.qb64b) # write to db else: # write receipt couple to database couple = cigar.verfer.qb64b + cigar.qb64b self.db.addRct(key=dgkey, val=couple) else: # no events to be receipted yet at that sn so escrow self.escrowUReceipt(serder, cigars, said=ked["d"]) # digest in receipt raise UnverifiedReceiptError("Unverified receipt={}.".format(ked))
[docs] def processReceiptCouples(self, serder, cigars, firner=None): """ Process attachment with receipt couple Parameters: serder is SerderKERI instance of receipted serialized event message to which receipts are attached from replay cigars is list of Cigar instances that contain receipt couple signature in .raw and public key in .verfer firner is Seqner instance of first seen ordinal, if provided lookup event by fn = firner.sn """ # fetch pre dig to process ked = serder.ked pre = serder.pre sn = serder.sn # Only accept receipt if event is latest event at sn. Means its been # first seen and is the most recent first seen with that sn if firner: ldig = self.db.getFe(key=fnKey(pre=pre, sn=firner.sn)) else: ldig = self.db.getKeLast(key=snKey(pre=pre, sn=sn)) # retrieve dig of last event at sn. if ldig is None: # escrow because event does not yet exist in database # # take advantage of fact that receipt and event have same pre, sn fields self.escrowUReceipt(serder, cigars, said=serder.said) # digest in receipt raise UnverifiedReceiptError("Unverified receipt={}.".format(ked)) ldig = bytes(ldig).decode("utf-8") # verify digs match # retrieve event by dig assumes if ldig is not None that event exists at ldig if not serder.compare(said=ldig): # mismatch events problem with replay raise ValidationError("Mismatch replay event at sn = {} with db." "".format(ked["s"])) # process each couple to verify sig and write to db for cigar in cigars: if cigar.verfer.transferable: # skip transferable verfers continue # skip invalid couplets if not self.lax and cigar.verfer.qb64 in self.prefixes: # own is receiptor if pre in self.prefixes: # skip own receipter on own event # sign own events not receipt them logger.info("Kevery process: skipped own receipt attachment" " on own event receipt=\n%s\n", serder.pretty()) continue # skip own receipt attachment on own event if not self.local: # own receipt on other event when not local logger.info("Kevery process: skipped own receipt attachment" " on nonlocal event receipt=\n%s\n", serder.pretty()) continue # skip own receipt attachment on non-local event if cigar.verfer.verify(cigar.raw, serder.raw): wits = self.fetchWitnessState(pre, sn) rpre = cigar.verfer.qb64 # prefix of receiptor if rpre in wits: # its a witness receipt index = wits.index(rpre) # create witness indexed signature and write to db wiger = Siger(raw=cigar.raw, index=index, verfer=cigar.verfer) self.db.addWig(key=dgKey(pre, ldig), val=wiger.qb64b) else: # write receipt couple to database couple = cigar.verfer.qb64b + cigar.qb64b self.db.addRct(key=dgKey(pre, ldig), val=couple)
[docs] def processReceiptTrans(self, serder, tsgs): """ Process one transferable validator receipt (chit) serder with attached sigers Parameters: serder is chit serder (transferable validator receipt message) tsgs is tist of tuples from extracted transferable indexed sig groups each converted group is tuple of (i,s,d) triple plus list of sigs Receipt dict labels vs # version string pre # qb64 prefix sn # hex string sequence number ilk # rct dig # qb64 digest of receipted event """ # fetch pre, dig,seal to process ked = serder.ked pre = serder.pre sn = serder.sn # Only accept receipt if for last seen version of event at sn ldig = self.db.getKeLast(key=snKey(pre=pre, sn=sn)) # retrieve dig of last event at sn. if ldig is None: # escrow because event does not yet exist in database # take advantage of fact that receipt and event have same pre, sn fields self.escrowTRGroups(serder, tsgs) raise UnverifiedTransferableReceiptError("Unverified receipt={}.".format(ked)) # retrieve event by dig assumes if ldig is not None that event exists at ldig ldig = bytes(ldig).decode("utf-8") lraw = self.db.getEvt(key=dgKey(pre=pre, dig=ldig)) lserder = serdering.SerderKERI(raw=bytes(lraw)) # verify digs match if not lserder.compare(said=ldig): # mismatch events problem with replay raise ValidationError("Mismatch receipt of event at sn = {} with db." "".format(sn)) for sprefixer, sseqner, saider, sigers in tsgs: # iterate over each tsg if not self.lax and sprefixer.qb64 in self.prefixes: # own is receipter if pre in self.prefixes: # skip own receipter of own event # sign own events not receipt them raise ValidationError("Own pre={} receipter of own event" " {}.".format(self.prefixes, serder.pretty())) if not self.local: # skip own receipts of nonlocal events raise ValidationError("Own pre={} receipter of nonlocal event " "{}.".format(self.prefixes, serder.pretty())) # receipted event in db so attempt to get receipter est evt # retrieve dig of last event at sn of est evt of receipter. sdig = self.db.getKeLast(key=snKey(pre=sprefixer.qb64b, sn=sseqner.sn)) if sdig is None: # receipter's est event not yet in receipters's KEL # so need cue to discover est evt KEL for receipter from watcher etc self.escrowTReceipts(serder, sprefixer, sseqner, saider, sigers) raise UnverifiedTransferableReceiptError("Unverified receipt: " "missing establishment event of transferable " "receipter for event={}." "".format(ked)) # retrieve last event itself of receipter est evt from sdig. sraw = self.db.getEvt(key=dgKey(pre=sprefixer.qb64b, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=saider.qb64): # endorser's dig not match event raise ValidationError("Bad trans indexed sig group at sn = {}" " for ksn = {}." "".format(sseqner.sn, sserder.ked)) # verify sigs and if so write receipt to database sverfers = sserder.verfers if not sverfers: raise ValidationError("Invalid receipter's est. event" " dig = {} from pre ={}, no keys." "".format(saider.qb64, sprefixer.qb64)) for siger in sigers: if siger.index >= len(sverfers): raise ValidationError("Index = {} to large for keys." "".format(siger.index)) siger.verfer = sverfers[siger.index] # assign verfer if siger.verfer.verify(siger.raw, lserder.raw): # verify sig # good sig so write receipt quadruple to database quadruple = sprefixer.qb64b + sseqner.qb64b + saider.qb64b + siger.qb64b self.db.addVrc(key=dgKey(pre=pre, dig=ldig), val=quadruple) # dups kept
[docs] def processReceiptQuadruples(self, serder, trqs, firner=None): """ Process one attachment quadruple that comprises a transferable receipt Parameters: serder is chit serder (transferable validator receipt message) trqs is list of tuples (quadruples) of form (prefixer, seqner, diger, siger) firner is Seqner instance of first seen ordinal, if provided lookup event by fn = firner.sn Seal labels i pre # qb64 prefix of receipter s sn # hex of sequence number of est event for receipter keys d dig # qb64 digest of est event for receipter keys """ # fetch pre, dig,seal to process ked = serder.ked pre = serder.pre sn = serder.sn if firner: # retrieve last event by fn ordinal ldig = self.db.getFe(key=fnKey(pre=pre, sn=firner.sn)) else: # Only accept receipt if for last seen version of receipted event at sn ldig = self.db.getKeLast(key=snKey(pre=pre, sn=sn)) # retrieve dig of last event at sn. for sprefixer, sseqner, saider, siger in trqs: # iterate over each trq if not self.lax and sprefixer.qb64 in self.prefixes: # own trans receipt quadruple (chit) if pre in self.prefixes: # skip own trans receipts of own events raise ValidationError("Own pre={} replay attached transferable " "receipt quadruple of own event {}." "".format(self.prefixes, serder.pretty())) if not self.local: # skip own trans receipt quadruples of nonlocal events raise ValidationError("Own pre={} seal in replay attached " "transferable receipt quadruples of nonlocal" " event {}.".format(self.prefixes, serder.pretty())) if ldig is not None and sprefixer.qb64 in self.kevers: # both receipted event and receipter in database so retreive if isinstance(ldig, memoryview): ldig = bytes(ldig).decode("utf-8") if not serder.compare(said=ldig): # mismatch events problem with replay raise ValidationError("Mismatch replay event at sn = {} with db." "".format(ked["s"])) # retrieve dig of last event at sn of receipter. sdig = self.db.getKeLast(key=snKey(pre=sprefixer.qb64b, sn=sseqner.sn)) if sdig is None: # receipter's est event not yet in receipter's KEL # receipter's seal event not in receipter's KEL self.escrowTRQuadruple(serder, sprefixer, sseqner, saider, siger) raise UnverifiedTransferableReceiptError("Unverified receipt: " "missing establishment event of transferable " "validator receipt quadruple for event={}." "".format(ked)) # retrieve last event itself of receipter sraw = self.db.getEvt(key=dgKey(pre=sprefixer.qb64b, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=saider.qb64): # seal dig not match event raise ValidationError("Bad trans receipt quadruple at sn = {}" " for rct = {}." "".format(sseqner.sn, sserder.ked)) # verify sigs and if so write quadruple to database sverfers = sserder.verfers if not sverfers: raise ValidationError("Invalid trans receipt quad est. event" " dig = {} for receipt from pre ={}, " "no keys." "".format(saider.qb64, sprefixer.qb64)) if siger.index >= len(sverfers): raise ValidationError("Index = {} to large for keys." "".format(siger.index)) siger.verfer = sverfers[siger.index] # assign verfer if not siger.verfer.verify(siger.raw, serder.raw): # verify sig logger.info("Kevery unescrow error: Bad trans receipt sig." "pre=%s sn=%x receipter=%s\n", pre, sn, sprefixer.qb64) raise ValidationError("Bad escrowed trans receipt sig at " "pre={} sn={:x} receipter={}." "".format(pre, sn, sprefixer.qb64)) # good sig so write receipt quadruple to database # Set up quadruple quadruple = sprefixer.qb64b + sseqner.qb64b + saider.qb64b + siger.qb64b self.db.addVrc(key=dgKey(pre, serder.said), val=quadruple) else: # escrow either receiptor or receipted event not yet in database self.escrowTRQuadruple(serder, sprefixer, sseqner, saider, siger) raise UnverifiedTransferableReceiptError("Unverified receipt: " "missing associated event for transferable " "validator receipt quadruple for event={}." "".format(ked))
[docs] def removeStaleReplyEndRole(self, saider): """ Process reply escrow at saider for route "/end/role" """ pass
[docs] def removeStaleReplyLocScheme(self, saider): """ Process reply escrow at saider for route "/loc/scheme" """ pass
[docs] def registerReplyRoutes(self, router): """ Register the routes for processing messages embedded in `rpy` event messages Parameters: router(Router): reply message router """ router.addRoute("/end/role/{action}", self, suffix="EndRole") router.addRoute("/loc/scheme", self, suffix="LocScheme") router.addRoute("/ksn/{aid}", self, suffix="KeyStateNotice")
[docs] def processReplyEndRole(self, *, serder, saider, route, cigars=None, tsgs=None, **kwargs): """ Process one reply message for route = /end/role/add or /end/role/cut 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 (SerderKERI): instance of reply msg (SAD) saider (Saider): 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 EndpointRecord: allowed: bool = False # True eid allowed (add), False eid disallowed (cut) name: str = "" # optional user friendly name of endpoint Reply Message: { "v" : "KERI10JSON00011c_", "t" : "rpy", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "/end/role/add", "a" : { "cid": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", "role": "watcher", # one of kering.Roles "eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE", } } { "v" : "KERI10JSON00011c_", "t" : "rpy", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "/end/role/cut", "a" : { "cid": "EaU6JR2nmwyZ-i0d8JZAoTNZH3ULvYAfSVPzhzS6b5CM", "role": "watcher", # one of kering.Roles "eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE", } } """ # reply specific logic if route.startswith("/end/role/add"): allowed = True elif route.startswith("/end/role/cut"): allowed = False else: # unsupported route raise ValidationError(f"Usupported route={route} in {Ilks.rpy} " f"msg={serder.ked}.") route = "/end/role" # escrow based on route base data = serder.ked['a'] for k in ("cid", "role", "eid"): if k not in data: raise ValidationError(f"Missing element={k} from attributes in" f" {Ilks.rpy} msg={serder.ked}.") cider = coring.Prefixer(qb64=data["cid"]) # raises error if unsupported code cid = cider.qb64 # controller authorizing eid at role role = data["role"] if role not in kering.Roles: raise ValidationError(f"Invalid role={role} from attributes in " f"{Ilks.rpy} msg={serder.ked}.") eider = coring.Prefixer(qb64=data["eid"]) # raises error if unsupported code eid = eider.qb64 # controller of endpoint at role aid = cid # authorizing attribution id keys = (aid, role, eid) osaider = self.db.eans.get(keys=keys) # get old said if any if osaider is not None and osaider.qb64b == saider.qb64b: # check idempotent osaider = None # BADA Logic accepted = self.rvy.acceptReply(serder=serder, saider=saider, route=route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: raise UnverifiedReplyError(f"Unverified end role reply. {serder.ked}") self.updateEnd(keys=keys, saider=saider, allowed=allowed) # update .eans and .ends
[docs] def processReplyLocScheme(self, *, serder, saider, route, cigars=None, tsgs=None): """ Process one reply message for route = /loc/scheme 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 (SerderKERI): instance of reply msg (SAD) saider (Saider): 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 EndAuthRecord cid: str = "" # identifier prefix of controller that authorizes endpoint roles: list[str] = field(default_factory=list) # str endpoint roles such as watcher, witness etc LocationRecord: url: str # full url including host:port/path?query scheme is optional cids: list[EndAuthRecord] = field(default_factory=list) # optional authorization record references Reply Message: { "v" : "KERI10JSON00011c_", "t" : "rpy", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "/loc/scheme", "a" : { "eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE", "scheme": "http", # one of kering.Schemes "url": "http://localhost:8080/watcher/wilma", } } { "v" : "KERI10JSON00011c_", "t" : "rpy", "d": "EZ-i0d8JZAoTNZH3ULaU6JR2nmwyvYAfSVPzhzS6b5CM", "dt": "2020-08-22T17:50:12.988921+00:00", "r" : "/loc/scheme", "a" : { "eid": "BrHLayDN-mXKv62DAjFLX1_Y5yEUe0vA9YPe_ihiKYHE", "scheme": "http", # one of kering.Schemes "url": "", # Nullifies } } """ # reply specific logic if not route.startswith("/loc/scheme"): raise ValidationError("Usupported route={} in {} msg={}." "".format(route, Ilks.rpy, serder.ked)) route = "/loc/scheme" # escrow based on route base data = serder.ked["a"] for k in ("eid", "scheme", "url"): if k not in data: raise ValidationError("Missing element={} from attributes in {} " "msg={}.".format(k, Ilks.rpy, serder.ked)) eider = coring.Prefixer(qb64=data["eid"]) # raises error if unsupported code eid = eider.qb64 # controller of endpoint at role scheme = data["scheme"] if scheme not in kering.Schemes: raise ValidationError("Invalid scheme={} from attributes in {} " "msg={}.".format(scheme, Ilks.rpy, serder.ked)) url = data["url"] splits = urlsplit(url) # empty scheme allowed in, will use scheme field if splits.scheme and splits.scheme != scheme: # non empty but not match raise ValidationError("Invalid url={} for scheme={} from attributes in {} " "msg={}.".format(url, scheme, Ilks.rpy, serder.ked)) # empty host port allowed will use default localhost:8080 aid = eid # authorizing attribution id keys = (aid, scheme) osaider = self.db.lans.get(keys=keys) # get old said if any # BADA Logic accepted = self.rvy.acceptReply(serder=serder, saider=saider, route=route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: raise UnverifiedReplyError(f"Unverified loc scheme reply. {serder.ked}") self.updateLoc(keys=keys, saider=saider, url=url) # update .lans and .locs
[docs] def processReplyKeyStateNotice(self, *, serder, saider, route, cigars=None, tsgs=None, **kwargs): """ Process one reply message for key state = /ksn Process one reply message for key state = /ksn 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 (SerderKERI): instance of reply msg (SAD) saider (Saider): 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" : "/ksn/EeS834LMlGVEOGR8WU3rzZ9M6HUv_vtF32pSXQXKP7jg", "a" : { "v": "KERI10JSON000274_", "i": "EeS834LMlGVEOGR8WU3rzZ9M6HUv_vtF32pSXQXKP7jg", "s": "1", "t": "ksn", "p": "ESORkffLV3qHZljOcnijzhCyRT0aXM2XHGVoyd5ST-Iw", "d": "EtgNGVxYd6W0LViISr7RSn6ul8Yn92uyj2kiWzt51mHc", "f": "1", "dt": "2021-11-04T12:55:14.480038+00:00", "et": "ixn", "kt": "1", "k": [ "DTH0PwWwsrcO_4zGe7bUR-LJX_ZGBTRsmP-ZeJ7fVg_4" ], "n": "E6qpfz7HeczuU3dAd1O9gPPS6-h_dCxZGYhU8UaDY2pc", "bt": "3", "b": [ "BGKVzj4ve0VSd8z_AmvhLg4lqcC_9WYX90k03q-R_Ydo", "BuyRFMideczFZoapylLIyCjSdhtqVb31wZkRKvPfNqkw", "Bgoq68HCmYNUDgOz4Skvlu306o_NY-NrYuKAVhk3Zh9c" ], "c": [], "ee": { "s": "0", "d": "ESORkffLV3qHZljOcnijzhCyRT0aXM2XHGVoyd5ST-Iw", "br": [], "ba": [] }, "di": "" } } """ cigars = cigars if cigars is not None else [] tsgs = tsgs if tsgs is not None else [] # reply specific logic if not route.startswith("/ksn"): raise ValidationError(f"Usupported route={route} in {Ilks.rpy} " f"msg={serder.ked}.") aid = kwargs["aid"] data = serder.ked["a"] try: ksr = KeyStateRecord._fromdict(d=data) except Exception as ex: raise ValidationError(f"Malformed key state notice = {data}.") from ex # fetch from serder to process pre = ksr.i sn = int(ksr.s, 16) # check source and ensure we should accept it baks = ksr.b wats = set() for _, habr in self.db.habs.getItemIter(): wats |= set(habr.watchers) # not in promiscuous mode if not self.lax: if aid != ksr.i and \ aid not in baks and \ aid not in wats: raise kering.UntrustedKeyStateSource("key state notice for {} from untrusted source {} " .format(ksr.pre, aid)) if ksr.i in self.kevers: kever = self.kevers[ksr.i] if int(ksr.s, 16) < kever.sner.num: raise ValidationError("Skipped stale key state at sn {} for {}." "".format(int(ksr.s, 16), ksr.i)) keys = (pre, aid,) osaider = self.db.knas.get(keys=keys) # get old said if any dater = coring.Dater(dts=ksr.dt) # BADA Logic accepted = self.rvy.acceptReply(serder=serder, saider=saider, route=route, aid=aid, osaider=osaider, cigars=cigars, tsgs=tsgs) if not accepted: raise UnverifiedReplyError(f"Unverified key state notice reply. {serder.ked}") ldig = self.db.getKeLast(key=snKey(pre=pre, sn=sn)) # retrieve dig of last event at sn. diger = coring.Diger(qb64=ksr.d) # Only accept key state if for last seen version of event at sn if ldig is not None: # escrow because event does not yet exist in database ldig = bytes(ldig) # retrieve last event itself of signer given sdig sraw = self.db.getEvt(key=dgKey(pre=pre, dig=ldig)) # assumes db ensures that sraw must not be none because sdig was in KE sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=diger.qb64b): # mismatch events problem with replay raise ValidationError(f"Mismatch keystate at sn = {int(ksr.s,16)}" f" with db.") ksaider = coring.Saider(qb64=diger.qb64) self.updateKeyState(aid=aid, ksr=ksr, saider=ksaider, dater=dater) self.cues.push(dict(kin="keyStateSaved", ksn=ksr._asdict()))
[docs] def updateEnd(self, keys, saider, allowed=None): """ Update end auth database .eans and end database .ends. Parameters: keys (tuple): of key strs for databases (cid, role, eid) saider (Saider): instance from said in reply serder (SAD) allowed (bool): True allow eid to be endpoint provided False otherwise """ # update .eans and .ends self.db.eans.pin(keys=keys, val=saider) # overwrite if ender := self.db.ends.get(keys=keys): # preexisting record ender.allowed = allowed # update allowed status else: # no preexisting record ender = basing.EndpointRecord(allowed=allowed) # create new record self.db.ends.pin(keys=keys, val=ender) # overwrite
[docs] def updateLoc(self, keys, saider, url): """ Update loc auth database .lans and loc database .locs. Parameters: keys (tuple): of key strs for databases (eid, scheme) saider (Saider): instance from said in reply serder (SAD) url (str): endpoint url """ self.db.lans.pin(keys=keys, val=saider) # overwrite if locer := self.db.locs.get(keys=keys): # preexisting record locer.url = url # update preexisting record else: # no preexisting record locer = basing.LocationRecord(url=url) # create new record self.db.locs.pin(keys=keys, val=locer) # overwrite
[docs] def updateKeyState(self, aid, ksr, saider, dater): """ Update Reply SAD in database given by by serder and associated databases for attached cig couple or sig quadruple. Overwrites val at key if already exists. Parameters: aid (str): identifier of key state ksr (KeyStateRecord): converted from key state notice dict in reply msg saider (Saider): instance from said in serder (SAD) dater (Dater): instance from date-time in serder (SAD) """ keys = (saider.qb64,) # Add source of ksn to the key for DATEs too... (source AID, ksn AID) self.db.kdts.put(keys=keys, val=dater) # first one idempotent self.db.ksns.pin(keys=keys, val=ksr) # first one idempotent # Add source of ksr to the key... (ksr AID, source aid) self.db.knas.pin(keys=(ksr.i, aid), val=saider) # overwrite
def removeKeyState(self, saider): if saider: keys = (saider.qb64,) self.db.ksns.rem(keys=keys) self.db.kdts.rem(keys=keys)
[docs] def processQuery(self, serder, source=None, sigers=None, cigars=None): """ Process query mode replay message for collective or single element query. Assume promiscuous mode for now. Parameters: serder (SerderKERI) is query message serder source (Prefixer) identifier prefix of querier sigers (list) of Siger instances of attached controller indexed sigs cigars (list) of Cigar instance of attached non-trans sigs """ 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 route == "logs": pre = qry["i"] src = qry["src"] anchor = qry["a"] if "a" in qry else None sn = int(qry["s"], 16) if "s" in qry else None if pre not in self.kevers: self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) kever = self.kevers[pre] if anchor: if not self.db.findAnchoringSealEvent(pre=pre, seal=anchor): self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) elif sn is not None: if kever.sner.num < sn or not self.db.fullyWitnessed(kever.serder): self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) msgs = list() # outgoing messages for msg in self.db.clonePreIter(pre=pre, fn=0): msgs.append(msg) if kever.delegator: cloner = self.db.clonePreIter(pre=kever.delegator, fn=0) # create iterator at 0 for msg in cloner: msgs.append(msg) if msgs: self.cues.push(dict(kin="replay", src=src, msgs=msgs, dest=source.qb64)) elif route == "ksn": pre = qry["i"] src = qry["src"] if pre not in self.kevers: self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) kever = self.kevers[pre] # get list of witness signatures to ensure we are presenting a fully witnessed event wigs = self.db.getWigs(dgKey(pre, kever.serder.saidb)) # list of wigs wigers = [Siger(qb64b=bytes(wig)) for wig in wigs] if len(wigers) < kever.toader.num: self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) rserder = reply(route=f"/ksn/{src}", data=kever.state()._asdict()) self.cues.push(dict(kin="reply", src=src, route="/ksn", serder=rserder, dest=source.qb64)) elif route == "mbx": pre = qry["i"] src = qry["src"] topics = qry["topics"] if pre not in self.kevers: self.escrowQueryNotFoundEvent(serder=serder, prefixer=source, sigers=sigers, cigars=cigars) raise QueryNotFoundError("Query not found error={}.".format(ked)) self.cues.push(dict(kin="stream", serder=serder, pre=pre, src=src, topics=topics)) # if pre in self.kevers: # kever = self.kevers[pre] # if src in kever.wits and src in self.db.prefixes: # We are a witness for identifier # self.cues.push(dict(kin="stream", serder=serder, pre=pre, src=src, topics=topics)) else: self.cues.push(dict(kin="invalid", serder=serder)) raise ValidationError("invalid query message {} for evt = {}".format(ilk, ked))
[docs] def fetchEstEvent(self, pre, sn): """ Returns SerderKERI instance of establishment event that is authoritative for event in KEL for pre at sn. Returns None if no event at sn accepted in KEL for pre Parameters: pre is qb64 of identifier prefix for KEL sn is int sequence number of event in KEL of pre """ found = False while not found: dig = bytes(self.db.getKeLast(key=snKey(pre, sn))) if not dig: return None # retrieve event by dig raw = bytes(self.db.getEvt(key=dgKey(pre=pre, dig=dig))) if not raw: return None serder = serdering.SerderKERI(raw=raw) # deserialize event raw if serder.ked["t"] in (Ilks.icp, Ilks.dip, Ilks.rot, Ilks.drt): return serder # establishment event so return sn = int(serder.ked["s"], 16) - 1 # set sn to previous event if sn < 0: # no more events return None
[docs] def escrowOOEvent(self, serder, sigers, seqner=None, saider=None, wigers=None): """ Update associated logs for escrow of Out-of-Order event Parameters: serder (SerderKERI): instance of event sigers (list): of Siger instance for event seqner (Seqner): instance of sn of event delegatint/issuing event if any saider (Saider): instance of dig of event delegatint/issuing event if any wigers (list): of witness signatures """ dgkey = dgKey(serder.preb, serder.saidb) self.db.putDts(dgkey, helping.nowIso8601().encode("utf-8")) self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) self.db.putEvt(dgkey, serder.raw) self.db.addOoe(snKey(serder.preb, serder.sn), serder.saidb) if wigers: self.db.putWigs(dgkey, [siger.qb64b for siger in wigers]) if seqner and saider: couple = seqner.qb64b + saider.qb64b self.db.putPde(dgkey, couple) # idempotent # log escrowed logger.info("Kevery process: escrowed out of order event=\n%s\n", json.dumps(serder.ked, indent=1))
[docs] def escrowQueryNotFoundEvent(self, prefixer, serder, sigers, cigars=None): """ Update associated logs for escrow of Out-of-Order event Parameters: prefixer (Prefixer): source of query message serder (SerderKERI): instance of event sigers (list): of Siger instance for event cigars (list): of non-transferable receipts """ cigars = cigars if cigars is not None else [] dgkey = dgKey(prefixer.qb64b, serder.saidb) self.db.putDts(dgkey, helping.nowIso8601().encode("utf-8")) self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) self.db.putEvt(dgkey, serder.raw) self.db.addQnf(dgkey, serder.saidb) for cigar in cigars: self.db.addRct(key=dgkey, val=cigar.verfer.qb64b + cigar.qb64b) # log escrowed logger.info("Kevery process: escrowed query not found event=\n%s\n", json.dumps(serder.ked, indent=1))
[docs] def escrowLDEvent(self, serder, sigers): """ Update associated logs for escrow of Likely Duplicitous event Parameters: serder is SerderKERI instance of event sigers is list of Siger instance for event """ dgkey = dgKey(serder.preb, serder.saidb) self.db.putDts(dgkey, helping.nowIso8601().encode("utf-8")) self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) self.db.putEvt(dgkey, serder.raw) self.db.addLde(snKey(serder.preb, serder.sn), serder.saidb) # log duplicitous logger.info("Kevery process: escrowed likely duplicitous event=\n%s\n", json.dumps(serder.ked, indent=1))
[docs] def escrowUWReceipt(self, serder, wigers, said): """ Update associated logs for escrow of Unverified Event Witness Receipt (non-transferable) Escrowed value is couple edig+wig where: edig is receipted event dig not serder.dig wig is witness indexed signature on receipted event with key pair derived from witness nontrans identifier prefix in witness list. Index is offset into witness list of latest establishment event for receipted event. Parameters: serder (SerderKERI): instance of receipt msg not receipted event wigers (list): of Siger instances for witness indexed signature of receipted event said (str) qb64 said of receipted event not serder.dig because serder is a receipt not the receipted event """ # note receipt dig algo may not match database dig also so must always # serder.compare to match. So receipts for same event may have different # digs of that event due to different algos. So the escrow may have # different dup at same key, sn. Escrow needs to include dig # so can compare digs from receipt and in database for receipted event # with different algos. Can't lookup event by dig for same reason. Must # lookup last event by sn not by dig. self.db.putDts(dgKey(serder.preb, said), helping.nowIso8601().encode("utf-8")) for wiger in wigers: # escrow each couple # don't know witness pre yet without witness list so no verfer in wiger # if wiger.verfer.transferable: # skip transferable verfers # continue # skip invalid triplets couple = said.encode("utf-8") + wiger.qb64b self.db.addUwe(key=snKey(serder.preb, serder.sn), val=couple) # log escrowed logger.info("Kevery process: escrowed unverified witness indexed receipt" " of pre= %s sn=%x dig=%s\n", serder.pre, serder.sn, said)
[docs] def escrowUReceipt(self, serder, cigars, said): """ Update associated logs for escrow of Unverified Event Receipt (non-transferable) Escrowed value is triple edig+rpre+cig where: edig is event dig rpre is nontrans receiptor prefix cig is non-indexed signature on event with key pair derived from rpre Parameters: serder (SerderKERI): instance of receipt msg not receipted event cigars (list): of Cigar instances for event receipt said (str): qb64 said in receipt of receipted event not serder.dig because serder is of receipt not receipted event """ # note receipt dig algo may not match database dig also so must always # serder.compare to match. So receipts for same event may have different # digs of that event due to different algos. So the escrow may have # different dup at same key, sn. Escrow needs to include dig # so can compare digs from receipt and in database for receipted event # with different algos. Can't lookup event by dig for same reason. Must # lookup last event by sn not by dig. self.db.putDts(dgKey(serder.preb, said), helping.nowIso8601().encode("utf-8")) for cigar in cigars: # escrow each triple if cigar.verfer.transferable: # skip transferable verfers continue # skip invalid triplets triple = said.encode("utf-8") + cigar.verfer.qb64b + cigar.qb64b self.db.addUre(key=snKey(serder.preb, serder.sn), val=triple) # should be snKey # log escrowed logger.info("Kevery process: escrowed unverified receipt of pre= %s " " sn=%x dig=%s\n", serder.pre, serder.sn, said)
[docs] def escrowTRGroups(self, serder, tsgs): """ Update associated logs for escrow of Transferable Receipt Groups for event (transferable) Parameters: serder instance of receipt message not receipted event tsgs is list of tuples of form: (prefixer,seqner,diger, sigers) prefixer is Prefixer instance of prefix of receipter seqner is Seqner instance of sn of est event of receiptor diger is Diger instance of digest of est event of receiptor sigers is list of Siger instances of multi-sig of receiptor escrow quintuple for each siger quintuple = edig+pre+snu+dig+sig where: edig is receipted event dig (serder.dig) pre is receipter prefix snu is receipter est event sn dig is receipt est evant dig sig is indexed sig of receiptor of receipted event """ # Receipt dig algo may not match database dig. So must always # serder.compare to match. So receipts for same event may have different # digs of that event due to different algos. So the escrow may have # different dup at same key, sn. Escrow needs to be quintuple with # edig, validator prefix, validtor est event sn, validator est evvent dig # and sig stored at kel pre, sn so can compare digs # with different algos. Can't lookup by dig for the same reason. Must # lookup last event by sn not by dig. for tsg in tsgs: prefixer, seqner, saider, sigers = tsg self.db.putDts(dgKey(serder.preb, serder.saidb), helping.nowIso8601().encode("utf-8")) # since serder of of receipt not receipted event must use dig in # serder.ked["d"] not serder.dig prelet = (serder.ked["d"].encode("utf-8") + prefixer.qb64b + seqner.qb64b + saider.qb64b) for siger in sigers: # escrow each quintlet quintuple = prelet + siger.qb64b # quintuple self.db.addVre(key=snKey(serder.preb, serder.sn), val=quintuple) # log escrowed logger.info("Kevery process: escrowed unverified transferable receipt " "of pre=%s sn=%x dig=%s by pre=%s\n", serder.pre, serder.sn, serder.ked["d"], prefixer.qb64)
[docs] def escrowTReceipts(self, serder, prefixer, seqner, saider, sigers): """ Update associated logs for escrow of Transferable Event Receipt Group (transferable) Parameters: serder instance of receipt message not receipted event prefixer is Prefixer instance of prefix of receipter seqner is Seqner instance of sn of est event of receiptor saider is Saider instance of said of est event of receiptor igers is list of Siger instances of multi-sig of receiptor escrow quintuple for each siger quintuple = edig+pre+snu+dig+sig where: edig is receipted event dig (serder.dig) pre is receipter prefix snu is receipter est event sn dig is receipt est evant dig sig is indexed sig of receiptor of receipted event """ # Receipt dig algo may not match database dig. So must always # serder.compare to match. So receipts for same event may have different # digs of that event due to different algos. So the escrow may have # different dup at same key, sn. Escrow needs to be quintuple with # edig, validator prefix, validtor est event sn, validator est evvent dig # and sig stored at kel pre, sn so can compare digs # with different algos. Can't lookup by dig for the same reason. Must # lookup last event by sn not by dig. self.db.putDts(dgKey(serder.preb, serder.saidb), helping.nowIso8601().encode("utf-8")) # since serder of of receipt not receipted event must use dig in # serder.ked["d"] not serder.dig prelet = (serder.ked["d"].encode("utf-8") + prefixer.qb64b + seqner.qb64b + saider.qb64b) for siger in sigers: # escrow each quintlet quintuple = prelet + siger.qb64b # quintuple self.db.addVre(key=snKey(serder.preb, serder.sn), val=quintuple) # log escrowed logger.info("Kevery process: escrowed unverified transferable receipt " "of pre=%s sn=%x dig=%s by pre=%s\n", serder.pre, serder.sn, serder.ked["d"], prefixer.qb64)
[docs] def escrowTRQuadruple(self, serder, sprefixer, sseqner, saider, siger): """ Update associated logs for escrow of Unverified Transferable Receipt (transferable) escrow quintuple made from quadruple where: quadruple = spre+ssnu+sdig+sig (s is trans receipt signer) quintuple = edig+spre+ssnu+sdig+sig (edig is signed event digest) Parameters: serder instance of receipt message not receipted event sigers is list of Siger instances attached to receipt message seal is SealEvent instance (namedTuple) saider is digest of receipted event provided in receipt """ # Receipt dig algo may not match database dig. So must always # serder.compare to match. So receipts for same event may have different # digs of that event due to different algos. So the escrow may have # different dup at same key, sn. Escrow needs to be quintuple with # edig, validator prefix, validtor est event sn, validator est evvent dig # and sig stored at kel pre, sn so can compare digs # with different algos. Can't lookup by dig for the same reason. Must # lookup last event by sn not by dig. self.db.putDts(dgKey(serder.preb, serder.said), helping.nowIso8601().encode("utf-8")) quintuple = (serder.saidb + sprefixer.qb64b + sseqner.qb64b + saider.qb64b + siger.qb64b) self.db.addVre(key=snKey(serder.preb, serder.sn), val=quintuple) # log escrowed logger.info("Kevery process: escrowed unverified transferabe validator " "receipt of pre= %s sn=%x dig=%s\n", serder.pre, serder.sn, serder.said)
[docs] def processEscrows(self): """ Iterate throush escrows and process any that may now be finalized Parameters: """ try: self.processEscrowOutOfOrders() self.processEscrowUnverWitness() self.processEscrowUnverNonTrans() self.processEscrowUnverTrans() self.processEscrowPartialWigs() self.processEscrowPartialSigs() self.processEscrowDuplicitous() self.processQueryNotFound() except Exception as ex: # log diagnostics errors etc if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery escrow process error: %s\n", ex.args[0]) else: logger.error("Kevery escrow process error: %s\n", ex.args[0]) raise ex
[docs] def processEscrowOutOfOrders(self): """ Process events escrowed by Kever that are recieved out-of-order. An event is out of order if its prior event has not been accepted into its KEL. Without the prior event there is no way to know the key state and therefore no way to verify signatures on the out-of-order event. Escrowed items are indexed in database table keyed by prefix and sn with duplicates given by different dig inserted in insertion order. This allows FIFO processing of events with same prefix and sn but different digest. Uses .db.addOoe(self, key, val) which is IOVal with dups. Value is dgkey for event stored in .Evt where .Evt has serder.raw of event. Original Escrow steps: dgkey = dgKey(pre, serder.dig) self.db.putDts(dgkey, nowIso8601().encode("utf-8")) self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) self.db.putEvt(dgkey, serder.raw) self.db.addOoe(snKey(pre, sn), serder.dig) where: serder is SerderKERI instance of event sigers is list of Siger instance for event pre is str qb64 of identifier prefix of event sn is int sequence number of event Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event Get and Attach Signatures Process event as if it came in over the wire If successful then remove from escrow table """ key = ekey = b'' # both start same. when not same means escrows found while True: # break when done for ekey, edig in self.db.getOoeItemsNextIter(key=key): try: pre, sn = splitKeySN(ekey) # get pre and sn from escrow item # check date if expired then remove escrow. dtb = self.db.getDts(dgKey(pre, bytes(edig))) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(bytes(edig))) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutOOE): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale event escrow " " at dig = %s\n", bytes(edig)) raise ValidationError("Stale event escrow " "at dig = {}.".format(bytes(edig))) # get the escrowed event using edig eraw = self.db.getEvt(dgKey(pre, bytes(edig))) if eraw is None: # no event so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgKey(pre, bytes(edig))) if not sigs: # otherwise its a list of sigs # no sigs so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event sigs at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt sigs at " "dig = {}.".format(bytes(edig))) # process event sigers = [Siger(qb64b=bytes(sig)) for sig in sigs] # get wigs wigs = self.db.getWigs(dgKey(pre, bytes(edig))) # list of wigs wigers = [Siger(qb64b=bytes(wig)) for wig in wigs] self.processEvent(serder=eserder, sigers=sigers, wigers=wigers) # If process does NOT validate event with sigs, becasue it is # still out of order then process will attempt to re-escrow # and then raise OutOfOrderError (subclass of ValidationError) # so we can distinquish between ValidationErrors that are # re-escrow vs non re-escrow. We want process to be idempotent # with respect to processing events that result in escrow items. # On re-escrow attempt by process, Ooe escrow is called by # Kevery.self.escrowOOEvent Which calls # self.db.addOoe(snKey(pre, sn), serder.digb) # which in turn will not enter dig as dup if one already exists. # So re-escrow attempt will not change the escrowed ooe db. # Non re-escrow ValidationError means some other issue so unescrow. # No error at all means processed successfully so also unescrow. except OutOfOrderError as ex: # still waiting on missing prior event to validate if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery 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.db.delOoe(snKey(pre, sn), edig) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delOoe(snKey(pre, sn), edig) # removes one escrow at key val logger.info("Kevery unescrow succeeded in valid event: " "event=\n%s\n", json.dumps(eserder.ked, indent=1)) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
[docs] def processEscrowPartialSigs(self): """ Process events escrowed by Kever that were only partially fulfilled, either due to missing signatures or missing dependent events like a delegating event. But event has at least one verified signature. Escrowed items are indexed in database table keyed by prefix and sequence number with duplicates inserted in insertion order. This allows FIFO processing of events with same prefix and sn. Uses .db.addPse(self, key, val) which is IOVal with dups. Value is dgkey for event stored in .Evt where .Evt has serder.raw of event. Original Escrow steps: dgkey = dgKey(pre, serder.digb) .db.putDts(dgkey, nowIso8601().encode("utf-8")) .db.putSigs(dgkey, [siger.qb64b for siger in sigers]) .db.putEvt(dgkey, serder.raw) .db.addPse(snKey(pre, sn), serder.digb) where: serder is SerderKERI instance of event sigers is list of Siger instance for event pre is str qb64 of identifier prefix of event sn is int sequence number of event Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event Get and Attach Signatures Process event as if it came in over the wire If successful then remove from escrow table """ key = ekey = b'' # both start same. when not same means escrows found while True: # break when done for ekey, edig in self.db.getPseItemsNextIter(key=key): eserder = None try: pre, sn = splitKeySN(ekey) # get pre and sn from escrow item dgkey = dgKey(pre, bytes(edig)) # check date if expired then remove escrow. dtb = self.db.getDts(dgkey) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(bytes(edig))) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutPSE): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale event escrow " " at dig = %s\n", bytes(edig)) raise ValidationError("Stale event escrow " "at dig = {}.".format(bytes(edig))) # get the escrowed event using edig eraw = self.db.getEvt(dgkey) if eraw is None: # no event so so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgkey) if not sigs: # otherwise its a list of sigs # no sigs so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event sigs at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt sigs at " "dig = {}.".format(bytes(edig))) # seal source (delegator issuer if any) delseqner = delsaider = None couple = self.db.getPde(dgkey) if couple is not None: delseqner, delsaider = deSourceCouple(couple) elif eserder.ked["t"] in (Ilks.dip, Ilks.drt,): if eserder.pre in self.kevers: delpre = self.kevers[eserder.pre].delegator else: delpre = eserder.ked["di"] seal = dict(i=eserder.ked["i"], s=eserder.snh, d=eserder.said) srdr = self.db.findAnchoringSealEvent(pre=delpre, seal=seal) if srdr is not None: delseqner = coring.Seqner(sn=srdr.sn) delsaider = coring.Saider(qb64=srdr.said) couple = delseqner.qb64b + delsaider.qb64b self.db.putPde(dgkey, couple) # process event sigers = [Siger(qb64b=bytes(sig)) for sig in sigs] self.processEvent(serder=eserder, sigers=sigers, delseqner=delseqner, delsaider=delsaider) # If process does NOT validate sigs or delegation seal (when delegated), # but there is still one valid signature then process will # attempt to re-escrow and then raise MissingSignatureError # or MissingDelegationSealError (subclass of ValidationError) # so we can distinquish between ValidationErrors that are # re-escrow vs non re-escrow. We want process to be idempotent # with respect to processing events that result in escrow items. # On re-escrow attempt by process, Pse escrow is called by # Kever.self.escrowPSEvent Which calls # self.db.addPse(snKey(pre, sn), serder.digb) # which in turn will not enter dig as dup if one already exists. # So re-escrow attempt will not change the escrowed pse db. # Non re-escrow ValidationError means some other issue so unescrow. # No error at all means processed successfully so also unescrow. except (MissingSignatureError, MissingDelegationError) as ex: # still waiting on missing sigs or missing seal to validate if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery unescrow failed: %s\n", ex.args[0]) except Exception as ex: # log diagnostics errors etc # error other than waiting on sigs or seal so remove from escrow self.db.delPse(snKey(pre, sn), edig) # removes one escrow at key val if eserder is not None and eserder.ked["t"] in (Ilks.dip, Ilks.drt,): self.cues.push(dict(kin="psUnescrow", serder=eserder)) if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delPse(snKey(pre, sn), edig) # removes one escrow at key val self.db.delPde(dgkey) # remove escrow if any if eserder is not None and eserder.ked["t"] in (Ilks.dip, Ilks.drt,): self.cues.push(dict(kin="psUnescrow", serder=eserder)) logger.info("Kevery unescrow succeeded in valid event: " "event=\n%s\n", json.dumps(eserder.ked, indent=1)) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
[docs] def processEscrowPartialWigs(self): """ Process events escrowed by Kever that were only partially fulfilled due to missing signatures from witnesses. Events only make into this escrow after fully signed and if delegated, delegation has been verified. Escrowed items in .pwes are indexed in database table keyed by prefix and sequence number with duplicates inserted in insertion order. This allows FIFO processing of events with same prefix and sn. Reads db.pwes .db.getPwe put there by .db.addPwe(self, key, val) which is IOVal with dups. Value is dgkey for event stored in .Evt where .Evt has serder.raw of event. Original Escrow steps: dgkey = dgKey(pre, serder.digb) .db.putDts(dgkey, nowIso8601().encode("utf-8")) .db.putWigs(dgkey, [siger.qb64b for siger in sigers]) .db.putEvt(dgkey, serder.raw) .db.addPwe(snKey(pre, sn), serder.digb) where: serder is SerderKERI instance of event wigers is list of Siger instance for of witnesses of event pre is str qb64 of identifier prefix of event sn is int sequence number of event Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event Get and Attach Signatures Get and Attach Witness Signatures Process event as if it came in over the wire If successful then remove from escrow table """ key = ekey = b'' # both start same. when not same means escrows found while True: # break when done for ekey, edig in self.db.getPweItemsNextIter(key=key): try: pre, sn = splitKeySN(ekey) # get pre and sn from escrow item # check date if expired then remove escrow. dtb = self.db.getDts(dgKey(pre, bytes(edig))) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(bytes(edig))) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutPWE): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale event escrow " " at dig = %s\n", bytes(edig)) raise ValidationError("Stale event escrow " "at dig = {}.".format(bytes(edig))) # get the escrowed event using edig eraw = self.db.getEvt(dgKey(pre, bytes(edig))) if eraw is None: # no event so so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs sigs = self.db.getSigs(dgKey(pre, bytes(edig))) # list of sigs if not sigs: # empty list # no sigs so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event sigs at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt sigs at " "dig = {}.".format(bytes(edig))) # get wigs wigs = self.db.getWigs(dgKey(pre, bytes(edig))) # list of wigs if not wigs: # empty list # wigs maybe empty while waiting for first witness signature # which may not arrive until some time after event is fully signed # so just log for debugging but do not unescrow by raising # ValidationError logger.info("Kevery unescrow wigs: No event wigs yet at." "dig = %s\n", bytes(edig)) # raise ValidationError("Missing escrowed evt wigs at " # "dig = {}.".format(bytes(edig))) # process event sigers = [Siger(qb64b=bytes(sig)) for sig in sigs] wigers = [Siger(qb64b=bytes(wig)) for wig in wigs] # seal source (delegator issuer if any) delseqner = delsaider = None couple = self.db.getPde(dgKey(pre, bytes(edig))) if couple is not None: delseqner, delsaider = deSourceCouple(couple) self.processEvent(serder=eserder, sigers=sigers, wigers=wigers, delseqner=delseqner, delsaider=delsaider) # If process does NOT validate wigs then process will attempt # to re-escrow and then raise MissingWitnessSignatureError # (subclass of ValidationError) # so we can distinquish between ValidationErrors that are # re-escrow vs non re-escrow. We want process to be idempotent # with respect to processing events that result in escrow items. # On re-escrow attempt by process, Pwe escrow is called by # Kever.self.escrowPWEvent Which calls # self.db.addPwe(snKey(pre, sn), serder.digb) # which in turn will NOT enter dig as dup if one already exists. # So re-escrow attempt will not change the escrowed pwe db. # Non re-escrow ValidationError means some other issue so unescrow. # No error at all means processed successfully so also unescrow. # Assumes that controller signature validation and delegation # validation will be successful as event would not be in # partially witnessed escrow unless they had already validated except MissingWitnessSignatureError as ex: # still waiting on missing witness sigs if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery unescrow failed: %s\n", ex.args[0]) except Exception as ex: # log diagnostics errors etc # error other than waiting on sigs or seal so remove from escrow self.db.delPwe(snKey(pre, sn), edig) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delPwe(snKey(pre, sn), edig) # removes one escrow at key val logger.info("Kevery unescrow succeeded in valid event: " "event=\n%s\n", json.dumps(eserder.ked, indent=1)) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
[docs] def processEscrowUnverWitness(self): """ Process escrowed unverified event receipts from witness receiptors A receipt is unverified if the associated event has not been accepted into its KEL. Without the event, there is no way to know where to store the receipt signatures neither to look up the witness list to verify the indexed signatures. The escrow is a couple with edig+wig where: edig is receipted event digest wig is witness indexed signature by key-pair derived from witness prefix in associated witness list. Index is offset into witness list of of latest establishment event for receipted event. The (unescrowed) verified receipt is stored as wig at event digest edig Escrowed items are indexed in database table keyed by prefix and sn with duplicates given by different receipt couple inserted in insertion order. This allows FIFO processing of escrows for events with same prefix and sn but different digest. Uses .uwes reads .db.getUwe was put there by.db.addUwe(self, key, val) which is IOVal with dups. Value is couple Original Escrow steps: self.db.putDts(dgKey(pre, dig), nowIso8601().encode("utf-8")) for wiger in wigers: # escrow each couple couple = dig.encode("utf-8") + wiger.qb64b self.db.addUwe(key=snKey(pre, sn), val=triple) where: dig is dig in receipt of receipted event wigers is list of Siger instances witness indexed signature of receipted event pre is str qb64 of identifier prefix of receipted event sn is int sequence number of receipted event Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event compare dig so same event verify wigs via wigers If successful then remove from escrow table """ ims = bytearray() key = ekey = b'' # both start same. when not same means escrows found while True: # break when done for ekey, ecouple in self.db.getUweItemsNextIter(key=key): try: pre, sn = splitKeySN(ekey) # get pre and sn from escrow db key # get escrowed receipt's rdiger of receipted event and # wiger indexed signature of receipted event rdiger, wiger = deWitnessCouple(ecouple) # check date if expired then remove escrow. dtb = self.db.getDts(dgKey(pre, bytes(rdiger.qb64b))) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", rdiger.qb64b) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(rdiger.qb64b)) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutUWE): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale event escrow " " at dig = %s\n", rdiger.qb64b) raise ValidationError("Stale event escrow " "at dig = {}.".format(rdiger.qb64b)) # lookup database dig of the receipted event in pwes escrow # using pre and sn lastEvt found = self._processEscrowFindUnver(pre=pre, sn=sn, rsaider=rdiger, wiger=wiger) if not found: # no partial witness escrow of event found # so keep in escrow by raising UnverifiedWitnessReceiptError logger.info("Kevery unescrow error: Missing witness " "receipted evt at pre=%s sn=%x\n", (pre, sn)) raise UnverifiedWitnessReceiptError("Missing witness " "receipted evt at pre={} sn={:x}".format(pre, sn)) except UnverifiedWitnessReceiptError as ex: # still waiting on missing prior event to validate # only happens if we process above if logger.isEnabledFor(logging.DEBUG): # adds exception data logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery 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.db.delUwe(snKey(pre, sn), ecouple) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): # adds exception data logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delUwe(snKey(pre, sn), ecouple) # removes one escrow at key val logger.info("Kevery unescrow succeeded for event pre=%s " "sn=%s\n", pre, sn) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
[docs] def processEscrowUnverNonTrans(self): """ Process escrowed unverified event receipts from nontrans receiptors A receipt is unverified if the associated event has not been accepted into its KEL. Without the event, there is no way to know where to store the receipts. The escrow is a triple with edig+rpre+cig where: edig is event digest rpre is receiptor (signer) of event cig is non-indexed signature by key-pair derived from rpre of event The verified receipt is just the couple rpre+cig that is stored by event digest edig Escrowed items are indexed in database table keyed by prefix and sn with duplicates given by different receipt triple inserted in insertion order. This allows FIFO processing of escrows for events with same prefix and sn but different digest. Uses .db.addUre(self, key, val) which is IOVal with dups. Value is triple Original Escrow steps: self.db.putDts(dgKey(pre, dig), nowIso8601().encode("utf-8")) for cigar in cigars: # escrow each triple if cigar.verfer.transferable: # skip transferable verfers continue # skip invalid couplets triple = dig.encode("utf-8") + cigar.verfer.qb64b + cigar.qb64b self.db.addUre(key=snKey(pre, sn), val=triple) # should be snKey where: dig is dig in receipt of receipted event cigars is list of cigars instances for receipted event pre is str qb64 of identifier prefix of receipted event sn is int sequence number of receipted event Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event compare dig so same event verify sigs via cigars If successful then remove from escrow table """ ims = bytearray() key = ekey = b'' # both start same. when not same means escrows found while True: # break when done for ekey, etriplet in self.db.getUreItemsNextIter(key=key): try: pre, sn = splitKeySN(ekey) # get pre and sn from escrow item rsaider, sprefixer, cigar = deReceiptTriple(etriplet) cigar.verfer = Verfer(qb64b=sprefixer.qb64b) # check date if expired then remove escrow. dtb = self.db.getDts(dgKey(pre, bytes(rsaider.qb64b))) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", rsaider.qb64b) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(rsaider.qb64b)) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutURE): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale event escrow " " at dig = %s\n", rsaider.qb64b) raise ValidationError("Stale event escrow " "at dig = {}.".format(rsaider.qb64b)) # Is receipt for unverified witnessed event in .Pwes escrow # if found then try else clause will remove from escrow found = self._processEscrowFindUnver(pre=pre, sn=sn, rsaider=rsaider, cigar=cigar) if not found: # no partial witness escrow of event found # so process as escrow of receipt for accept event # not two stage witnessed event escrow # get dig of receipted accepted event in kel using lastEvt # at pre and sn dig = self.db.getKeLast(snKey(pre, sn)) if dig is None: # no receipted event so keep in escrow logger.info("Kevery unescrow error: Missing receipted " "event at pre=%s sn=%x\n", pre, sn) raise UnverifiedReceiptError("Missing receipted evt " "at pre={} sn={:x}".format(pre, sn)) # get receipted event using pre and edig raw = self.db.getEvt(dgKey(pre, dig)) if raw is None: # receipted event superseded so remove from escrow logger.info("Kevery unescrow error: Invalid receipted " "event refereance at pre=%s sn=%x\n", pre, sn) raise ValidationError("Invalid receipted evt reference" " at pre={} sn={:x}".format(pre, sn)) serder = serdering.SerderKERI(raw=bytes(raw)) # receipted event # compare digs if rsaider.qb64b != serder.saidb: logger.info("Kevery unescrow error: Bad receipt dig." "pre=%s sn=%x receipter=%s\n", pre, sn, sprefixer.qb64) raise ValidationError("Bad escrowed receipt dig at " "pre={} sn={:x} receipter={}." "".format(pre, sn, sprefixer.qb64)) # verify sig verfer key is prefixer from triple if not cigar.verfer.verify(cigar.raw, serder.raw): # no sigs so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Bad receipt sig." "pre=%s sn=%x receipter=%s\n", pre, sn, sprefixer.qb64) raise ValidationError("Bad escrowed receipt sig at " "pre={} sn={:x} receipter={}." "".format(pre, sn, sprefixer.qb64)) # get current wits from kever state assuming not stale # receipt. Need function here to compute wits for actual # state at pre, sn. XXXX wits = self.kevers[serder.pre].wits rpre = cigar.verfer.qb64 # prefix of receiptor if rpre in wits: # its a witness receipt # this only works for extra receipts that come in later # after event is out of .Pwes escrow index = wits.index(rpre) # create witness indexed signature and write to db wiger = Siger(raw=cigar.raw, index=index, verfer=cigar.verfer) self.db.addWig(key=dgKey(pre, serder.said), val=wiger.qb64b) else: # write receipt couple to database couple = cigar.verfer.qb64b + cigar.qb64b self.db.addRct(key=dgKey(pre, serder.said), val=couple) except UnverifiedReceiptError as ex: # still waiting on missing prior event to validate # only happens if we process above if logger.isEnabledFor(logging.DEBUG): # adds exception data logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery 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.db.delUre(snKey(pre, sn), etriplet) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): # adds exception data logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delUre(snKey(pre, sn), etriplet) # removes one escrow at key val logger.info("Kevery unescrow succeeded for event pre=%s " "sn=%s\n", pre, sn) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
[docs] def processQueryNotFound(self): """ Process qry events escrowed by Kevery for KELs that have not yet met the criteria of the query. A missing KEL or criteria for an event in a KEL at a particular sequence number or an event containing a specific anchor can result in query not found escrowed events. Escrowed items are indexed in database table keyed by prefix and sn with duplicates given by different dig inserted in insertion order. This allows FIFO processing of events with same prefix and sn but different digest. Uses .db.addQnf(self, key, val) which is IOVal with dups. Value is dgkey for event stored in .Evt where .Evt has serder.raw of event. Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event Get and Attach Signatures Process event as if it came in over the wire If successful then remove from escrow table """ key = ekey = b'' # both start same. when not same means escrows found pre = b'' sn = 0 while True: # break when done for ekey, edig in self.db.getQnfItemsNextIter(key=key): try: pre, _ = splitKey(ekey) # get pre and sn from escrow item # check date if expired then remove escrow. dtb = self.db.getDts(dgKey(pre, bytes(edig))) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(bytes(edig))) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutQNF): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale qry event escrow " " at dig = %s\n", bytes(edig)) raise ValidationError("Stale qry event escrow " "at dig = {}.".format(bytes(edig))) # get the escrowed event using edig eraw = self.db.getEvt(dgKey(pre, bytes(edig))) if eraw is None: # no event so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgKey(pre, bytes(edig))) if not sigs: # otherwise its a list of sigs # no sigs so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event sigs at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt sigs at " "dig = {}.".format(bytes(edig))) # process event sigers = [Siger(qb64b=bytes(sig)) for sig in sigs] # get wigs cigars = [] cigs = self.db.getRcts(dgKey(pre, bytes(edig))) # list of wigs for cig in cigs: (_, cigar) = deReceiptCouple(cig) cigars.append(cigar) source = coring.Prefixer(qb64b=pre) self.processQuery(serder=eserder, source=source, sigers=sigers, cigars=cigars) except QueryNotFoundError as ex: # still waiting on missing prior event to validate if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery 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.db.delQnf(dgKey(pre, edig), edig) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delQnf(dgKey(pre, edig), edig) # removes one escrow at key val logger.info("Kevery unescrow succeeded in valid event: " "event=\n%s\n", json.dumps(eserder.ked, indent=1)) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
def _processEscrowFindUnver(self, pre, sn, rsaider, wiger=None, cigar=None): """ ToDo XXXX Incomplete Placeholder Support method called by other processEscowUnverXXX Receipt methods to find escrowed serder in .Pwes for unverifiable receipt due to signed but partially witnessed event Returns: found (bool): True means found matching event in .Pwes and added wig to .Wigs. False means dig not find matching event in .Pwes Raises: Validation error if found matching event but signature does not verify Parameters: pre (Union[str,bytes]): pre of receipted event controller kel sn (int): sequence number of receipted event rsaider (Saider): derived from receipt's dig of receipted event to find wiger (Siger): instance of witness indexed signature from receipt cigar (Cigar): instance of witness nonindexed signature from receipt """ # lookup the database dig of the receipted event in pwes escrow using # snKey(pre,sn) where pre is controller and sn is event sequence number # compare dig to rdiger derived from receipt's dig of receipted event found = False for dig in self.db.getPwesIter(key=snKey(pre, sn)): # search entries dig = bytes(dig) # database dig of receipted event # get the escrowed event using database dig in .Pwes serder = serdering.SerderKERI(raw=bytes(self.db.getEvt(dgKey(pre, dig)))) # receipted event # compare digs to ensure database dig and rdiger (receipt's dig) match if rsaider.qb64b != dig: continue # not match keep looking # Extract or compute witness list if serder.ked['t'] in (Ilks.icp, Ilks.dip): # inception get from event wits = serder.ked['b'] # get wits from event itself if len(oset(wits)) != len(wits): raise ValidationError("Invalid wits = {}, has duplicates for evt = {}." "".format(wits, serder.ked)) elif serder.ked['t'] in (Ilks.rot, Ilks.drt): # rotation compute from state # calculate wits from rotation and kever key state. wits = self.kevers[serder.pre].wits # get wits from key state cuts = serder.ked['br'] adds = serder.ked['ba'] witset = oset(wits) cutset = oset(cuts) addset = oset(adds) if len(cutset) != len(cuts): raise ValidationError("Invalid cuts={}, has duplicates " "for evt={}.".format(cuts, serder.ked)) if (witset & cutset) != cutset: # some cuts not in wits raise ValidationError("Invalid cuts={}, not all members " "in wits for evt={}.".format(cuts, serder.ked)) if len(addset) != len(adds): raise ValidationError("Invalid adds={}, has duplicates " "for evt={}.".format(adds, serder.ked)) if cutset & addset: # non empty intersection raise ValidationError("Intersecting cuts={} and adds={} " "for evt={}.".format(cuts, adds, serder.ked)) if witset & addset: # non empty intersection raise ValidationError("Intersecting wits={} and adds={} " "for evt={}.".format(self.wits, adds, serder.ked)) wits = list((witset - cutset) | addset) else: # interaction so get wits from kever key state # would not be in this escrow if out of order event wits = self.kevers[serder.pre].wits # get wits fromkey state if cigar: # if recipter is a witness make wiger rpre = cigar.verfer.qb64 # prefix of receiptor if rpre in wits: # its a witness receipt index = wits.index(rpre) # create witness indexed signature wiger from cigar and wit index wiger = Siger(raw=cigar.raw, index=index, verfer=cigar.verfer) found = True break # done with search have caller add wig. elif wiger: # check index and assign verfier to wiger if wiger.index >= len(wits): # bad index # raise ValidationError which removes from escrow by caller logger.info("Kevery unescrow error: Bad witness receipt" " index=%i for pre=%s sn=%x\n", wiger.index, pre, sn) raise ValidationError("Bad escrowed witness receipt index={}" " at pre={} sn={:x}.".format(wiger.index, pre, sn)) wiger.verfer = Verfer(qb64=wits[wiger.index]) found = True break # done with search have caller add wig. if found: # verify signature and if verified write to .Wigs if not wiger.verfer.verify(wiger.raw, serder.raw): # not verify # raise ValidationError which unescrows .Uwes or .Ures in caller logger.info("Kevery unescrow error: Bad witness receipt" " wig. pre=%s sn=%x\n", pre, sn) raise ValidationError("Bad escrowed witness receipt wig" " at pre={} sn={:x}." "".format(pre, sn)) self.db.addWig(key=dgKey(pre, serder.said), val=wiger.qb64b) # processEscrowPartialWigs removes from this .Pwes escrow # when fully witnessed using self.db.delPwe(snkey, dig) return found
[docs] def processEscrowUnverTrans(self): """ Process event receipts from transferable identifiers (validators) escrowed by Kever that are unverified. A transferable receipt is unverified if either the receipted event has not been accepted into the receipted's KEL or the establishment event of the receiptor has not been accepted into the receipter's KEL. Without either event there is no way to know where to store the receipt quadruples. The escrow is a quintuple with dig+spre+ssnu+sdig+sig the verified receipt is just the quadruple spre+ssnu+sdig+sig that is stored by event dig Escrowed items are indexed in database table keyed by prefix and sn with duplicates given by different receipt quintuple inserted in insertion order. This allows FIFO processing of escrows of events with same prefix and sn but different digest. Uses .db.addVre(self, key, val) which is IOVal with dups. Value is quintuple Original Escrow steps: self.db.putDts(dgKey(serder.preb, dig), nowIso8601().encode("utf-8")) prelet = (dig.encode("utf-8") + seal.i.encode("utf-8") + Seqner(sn=int(seal.s, 16)).qb64b + seal.d.encode("utf-8")) for siger in sigers: # escrow each quintlet quintuple = prelet + siger.qb64b # quintuple self.db.addVre(key=snKey(serder.preb, serder.sn), val=quintuple) where: dig is dig in receipt of receipted event sigers is list of Siger instances for receipted event Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event compare dig so same event verify sigs via sigers If successful then remove from escrow table """ ims = bytearray() key = ekey = b'' # both start same. when not same means escrows found while True: # break when done for ekey, equinlet in self.db.getVreItemsNextIter(key=key): try: pre, sn = splitKeySN(ekey) # get pre and sn from escrow item esaider, sprefixer, sseqner, ssaider, siger = deTransReceiptQuintuple(equinlet) # check date if expired then remove escrow. dtb = self.db.getDts(dgKey(pre, bytes(esaider.qb64b))) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", esaider.qb64b) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(esaider.qb64b)) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutVRE): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale event escrow " " at dig = %s\n", esaider.qb64b) raise ValidationError("Stale event escrow " "at dig = {}.".format(esaider.qb64b)) # get dig of the receipted event using pre and sn lastEvt raw = self.db.getKeLast(snKey(pre, sn)) if raw is None: # no event so keep in escrow logger.info("Kevery unescrow error: Missing receipted " "event at pre=%s sn=%x\n", pre, sn) raise UnverifiedTransferableReceiptError("Missing receipted evt at pre={} " " sn={:x}".format(pre, sn)) dig = bytes(raw) # get receipted event using pre and edig raw = self.db.getEvt(dgKey(pre, dig)) if raw is None: # receipted event superseded so remove from escrow logger.info("Kevery unescrow error: Invalid receipted " "event referenace at pre=%s sn=%x\n", pre, sn) raise ValidationError("Invalid receipted evt reference " "at pre={} sn={:x}".format(pre, sn)) serder = serdering.SerderKERI(raw=bytes(raw)) # receipted event # compare digs if esaider.qb64b != serder.saidb: logger.info("Kevery unescrow error: Bad receipt dig." "pre=%s sn=%x receipter=%s\n", (pre, sn, sprefixer.qb64)) raise ValidationError("Bad escrowed receipt dig at " "pre={} sn={:x} receipter={}." "".format(pre, sn, sprefixer.qb64)) # get receipter's last est event # retrieve dig of last event at sn of receipter. sdig = self.db.getKeLast(key=snKey(pre=sprefixer.qb64b, sn=sseqner.sn)) if sdig is None: # no event so keep in escrow logger.info("Kevery unescrow error: Missing receipted " "event at pre=%s sn=%x\n", pre, sn) raise UnverifiedTransferableReceiptError("Missing receipted evt at pre={} " " sn={:x}".format(pre, sn)) # retrieve last event itself of receipter sraw = self.db.getEvt(key=dgKey(pre=sprefixer.qb64b, dig=bytes(sdig))) # assumes db ensures that sraw must not be none because sdig was in KE sserder = serdering.SerderKERI(raw=bytes(sraw)) if not sserder.compare(said=ssaider.qb64): # seal dig not match event # this unescrows raise ValidationError("Bad chit seal at sn = {} for rct = {}." "".format(sseqner.sn, sserder.ked)) # verify sigs and if so write quadruple to database verfers = sserder.verfers if not verfers: raise ValidationError("Invalid seal est. event dig = {} for " "receipt from pre ={} no keys." "".format(ssaider.qb64, sprefixer.qb64)) # Set up quadruple sealet = sprefixer.qb64b + sseqner.qb64b + ssaider.qb64b if siger.index >= len(verfers): raise ValidationError("Index = {} to large for keys." "".format(siger.index)) siger.verfer = verfers[siger.index] # assign verfer if not siger.verfer.verify(siger.raw, serder.raw): # verify sig logger.info("Kevery unescrow error: Bad trans receipt sig." "pre=%s sn=%x receipter=%s\n", pre, sn, sprefixer.qb64) raise ValidationError("Bad escrowed trans receipt sig at " "pre={} sn={:x} receipter={}." "".format(pre, sn, sprefixer.qb64)) # good sig so write receipt quadruple to database quadruple = sealet + siger.qb64b self.db.addVrc(key=dgKey(pre, serder.said), val=quadruple) except UnverifiedTransferableReceiptError as ex: # still waiting on missing prior event to validate # only happens if we process above if logger.isEnabledFor(logging.DEBUG): # adds exception data logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery 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.db.delVre(snKey(pre, sn), equinlet) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): # adds exception data logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delVre(snKey(pre, sn), equinlet) # removes one escrow at key val logger.info("Kevery unescrow succeeded for event = %s\n", serder.ked) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
[docs] def processEscrowDuplicitous(self): """ Process events escrowed by Kever that are likely duplicitous. An event is likely duplicitous if a different version of event already has been accepted into the KEL. Escrowed items are indexed in database table keyed by prefix and sn with duplicates given by different dig inserted in insertion order. This allows FIFO processing of events with same prefix and sn but different digest. Uses .db.addLde(self, key, val) which is IOVal with dups. Value is dgkey for event stored in .Evt where .Evt has serder.raw of event. Original Escrow steps: dgkey = dgKey(pre, serder.dig) self.db.putDts(dgkey, nowIso8601().encode("utf-8")) self.db.putSigs(dgkey, [siger.qb64b for siger in sigers]) self.db.putEvt(dgkey, serder.raw) self.db.addLde(snKey(pre, sn), serder.digb) where: serder is SerderKERI instance of event sigers is list of Siger instance for event pre is str qb64 of identifier prefix of event sn is int sequence number of event Steps: Each pass (walk index table) For each prefix,sn For each escrow item dup at prefix,sn: Get Event Get and Attach Signatures Process event as if it came in over the wire If successful then remove from escrow table """ key = ekey = b'' # both start same. when not same means escrows found while True: # break when done for ekey, edig in self.db.getLdeItemsNextIter(key=key): try: pre, sn = splitKeySN(ekey) # get pre and sn from escrow item # check date if expired then remove escrow. dtb = self.db.getDts(dgKey(pre, bytes(edig))) if dtb is None: # othewise is a datetime as bytes # no date time so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event datetime" " at dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed event datetime " "at dig = {}.".format(bytes(edig))) # do date math here and discard if stale nowIso8601() bytes dtnow = helping.nowUTC() dte = helping.fromIso8601(bytes(dtb)) if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutLDE): # escrow stale so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Stale event escrow " " at dig = %s\n", bytes(edig)) raise ValidationError("Stale event escrow " "at dig = {}.".format(bytes(edig))) # get the escrowed event using edig eraw = self.db.getEvt(dgKey(pre, bytes(edig))) if eraw is None: # no event so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt at dig = {}." "".format(bytes(edig))) eserder = serdering.SerderKERI(raw=bytes(eraw)) # escrowed event # get sigs and attach sigs = self.db.getSigs(dgKey(pre, bytes(edig))) if not sigs: # otherwise its a list of sigs # no sigs so raise ValidationError which unescrows below logger.info("Kevery unescrow error: Missing event sigs at." "dig = %s\n", bytes(edig)) raise ValidationError("Missing escrowed evt sigs at " "dig = {}.".format(bytes(edig))) sigers = [Siger(qb64b=bytes(sig)) for sig in sigs] self.processEvent(serder=eserder, sigers=sigers) # If process does NOT validate event with sigs, becasue it is # still out of order then process will attempt to re-escrow # and then raise OutOfOrderError (subclass of ValidationError) # so we can distinquish between ValidationErrors that are # re-escrow vs non re-escrow. We want process to be idempotent # with respect to processing events that result in escrow items. # On re-escrow attempt by process, Ooe escrow is called by # Kevery.self.escrowOOEvent Which calls # self.db.addOoe(snKey(pre, sn), serder.digb) # which in turn will not enter dig as dup if one already exists. # So re-escrow attempt will not change the escrowed ooe db. # Non re-escrow ValidationError means some other issue so unescrow. # No error at all means processed successfully so also unescrow. except LikelyDuplicitousError as ex: # still can't determine if duplicitous if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrow failed: %s\n", ex.args[0]) else: logger.error("Kevery unescrow failed: %s\n", ex.args[0]) except Exception as ex: # log diagnostics errors etc # error other than likely duplicitous so remove from escrow self.db.delLde(snKey(pre, sn), edig) # removes one escrow at key val if logger.isEnabledFor(logging.DEBUG): logger.exception("Kevery unescrowed: %s\n", ex.args[0]) else: logger.error("Kevery unescrowed: %s\n", 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.db.delLde(snKey(pre, sn), edig) # removes one escrow at key val logger.info("Kevery unescrow succeeded in valid event: " "event=\n%s\n", json.dumps(eserder.ked, indent=1)) if ekey == key: # still same so no escrows found on last while iteration break key = ekey # setup next while iteration, with key after ekey
[docs] def duplicity(self, serder, sigers): """ PlaceHolder Reminder Processes potential duplicitous events in PDELs Handles duplicity detection and logging if duplicitous Placeholder here for logic need to move """ pass
[docs] def loadEvent(db, preb, dig): """ Load event details from database Args: db (Baser): database to load event fro, preb (bytes): qb64b identifier prefix dig (bytes): digest of event to load Returns: dict: data from event """ event = dict() dgkey = dbing.dgKey(preb, dig) # get message if not (raw := db.getEvt(key=dgkey)): raise ValueError("Missing event for dig={}.".format(dig)) serder = serdering.SerderKERI(raw=bytes(raw)) event["ked"] = serder.ked sn = serder.sn sdig = db.getKeLast(key=dbing.snKey(pre=preb, sn=sn)) if sdig is not None: event["stored"] = True # add indexed signatures to attachments sigs = db.getSigs(key=dgkey) dsigs = [] for s in sigs: sig = coring.Siger(qb64b=bytes(s)) dsigs.append(dict(index=sig.index, signature=sig.qb64)) event["signatures"] = dsigs # add witness state at this event wits = db.wits.get(dgkey) if serder.estive else [] event["witnesses"] = [wit.qb64 for wit in wits] # add indexed witness signatures to attachments dwigs = [] if wigs := db.getWigs(key=dgkey): for w in wigs: sig = coring.Siger(qb64b=bytes(w)) dwigs.append(dict(index=sig.index, signature=sig.qb64)) event["witness_signatures"] = dwigs # add authorizer (delegator/issuer) source seal event couple to attachments couple = db.getAes(dgkey) if couple is not None: raw = bytearray(couple) seqner = coring.Seqner(qb64b=raw, strip=True) saider = coring.Saider(qb64b=raw) event["source_seal"] = dict(sequence=seqner.sn, said=saider.qb64) receipts = dict() # add trans receipts quadruples if quads := db.getVrcs(key=dgkey): trans = [] for quad in quads: raw = bytearray(quad) trans.append(dict( prefix=coring.Prefixer(qb64b=raw, strip=True).qb64, sequence=coring.Seqner(qb64b=raw, strip=True).qb64, said=coring.Saider(qb64b=raw, strip=True).qb64, signature=coring.Siger(qb64b=raw, strip=True).qb64, )) receipts["transferable"] = trans # add nontrans receipts couples if coups := db.getRcts(key=dgkey): nontrans = [] for coup in coups: raw = bytearray(coup) (prefixer, cigar) = deReceiptCouple(raw, strip=True) nontrans.append(dict(prefix=prefixer.qb64, signature=cigar.qb64)) receipts["nontransferable"] = nontrans event["receipts"] = receipts # add first seen replay couple to attachments if not (dts := db.getDts(key=dgkey)): raise ValueError("Missing datetime for dig={}.".format(dig)) event["timestamp"] = coring.Dater(dts=bytes(dts)).dts return event