# -*- encoding: utf-8 -*-
"""
keri.peer.exchanging module
"""
import datetime
import logging
from datetime import timedelta
from hio.help import decking, ogler
from ..kering import (Vrsn_1_0, Vrsn_2_0, Ilks,
Kinds, Version, versify,
ValidationError, MissingSignatureError)
from ..core import (Counter, Pather, Dater, Diger,
Prefixer, Seqner, Saider,
Noncer, Sadder, SerderKERI,
NonTransDex, Saids, Codens,
verifySigs)
from ..db import fetchTsgs
from ..help import helping
ExchangeMessageTimeWindow = timedelta(seconds=300)
logger = ogler.getLogger()
[docs]
class Exchanger:
"""
Peer to Peer KERI message Exchanger.
"""
TimeoutPSE = 10 # seconds to timeout partially signed or delegated escrows
[docs]
def __init__(self, hby, handlers, cues=None, delta=ExchangeMessageTimeWindow):
""" Initialize instance
Parameters:
hby (Haberyu): database environment
handlers(list): list of Handlers capable of responding to exn messages
cues (Deck): of Cues i.e. notices of requests needing response
delta (timedelta): message timeout window
"""
self.hby = hby
self.kevers = self.hby.db.kevers
self.delta = delta
self.routes = dict()
self.cues = cues if cues is not None else decking.Deck() # subclass of deque
for handler in handlers:
if handler.resource in self.routes:
raise ValidationError("unable to register behavior {}, it has already been registered"
"".format(handler.resource))
self.routes[handler.resource] = handler
def addHandler(self, handler):
if handler.resource in self.routes:
raise ValidationError("unable to register behavior {}, it has already been registered"
"".format(handler.resource))
self.routes[handler.resource] = handler
[docs]
def processEvent(self, serder, tsgs=None, cigars=None, ptds=None, essrs=None, **kwa):
""" Process one serder event with attached indexed signatures representing a Peer to Peer exchange message.
Parameters:
serder (Serder): instance of event to process
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
cigars (list): of Cigar instances of attached non-trans sigs
ptds (list[bytes]): pathed Cesr Streams
essrs (list[Texter]): ESSR streams as Texters
"""
ptds = ptds if ptds is not None else []
essrs = essrs if essrs is not None else []
route = serder.ked["r"]
sender = serder.ked["i"]
behavior = self.routes[route] if route in self.routes else None
if tsgs:
for prefixer, snumber, sdiger, sigers in tsgs: # iterate over each tsg
if sender != prefixer.qb64: # sig not by aid
msg = (f"Skipped signature not from aid = "
f"{sender}, from {prefixer.qb64} on exn msg = {serder.said}")
logger.info(msg)
logger.debug("Exchange message body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
if prefixer.qb64 not in self.kevers or self.kevers[prefixer.qb64].sn < snumber.sn:
if self.escrowPSEvent(serder=serder, tsgs=tsgs, pathed=ptds):
self.cues.append(dict(kin="query", q=dict(r="logs", pre=prefixer.qb64, sn=snumber.snh)))
msg = f"Unable to find sender {prefixer.qb64} in kevers for evt = {serder.said}"
logger.info(msg)
logger.debug("Exchange message body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
# Verify the signatures are valid and that the signature threshold as of the signing event is met
tholder, verfers = self.hby.db.resolveVerifiers(pre=prefixer.qb64, sn=snumber.sn, dig=sdiger.qb64)
_, indices = verifySigs(serder.raw, sigers, verfers)
if not tholder.satisfy(indices): # We still don't have all the sigers, need to escrow
if self.escrowPSEvent(serder=serder, tsgs=tsgs, pathed=ptds):
self.cues.append(dict(kin="query", q=dict(r="logs", pre=prefixer.qb64, sn=snumber.snh)))
msg = (f"Not enough signatures in idx={indices} route={route} "
f"for evt = {serder.said} receiver={serder.ked.get('rp', '')}")
logger.info(msg)
logger.debug("Exchange message body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
elif cigars:
for cigar in cigars:
if sender != cigar.verfer.qb64: # cig not by aid
msg = (f"Skipped cig not from aid={sender} route={route} "
f"for exn evt = {serder.said} receiver={serder.ked.get('rp', '')}")
logger.info(msg)
logger.debug("Exchange message body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
if not cigar.verfer.verify(cigar.raw, serder.raw): # cig not verify
msg = (f"Failure satisfying exn on cigs for {cigar} route={route} "
f"for evt = {serder.said} receiver={serder.ked.get('rp', '')}")
logger.info(msg)
logger.debug("Exchange message body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
else:
self.escrowPSEvent(serder=serder, tsgs=[], pathed=ptds)
msg = (
f"Failure satisfying exn, no cigs or sigs for evt = {serder.said} "
f"on route {route} receiver = {serder.ked.get('rp', '')}")
logger.info(msg)
logger.debug("Exchange message body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
e = Pather(parts=["e"])
kwa = dict()
attachments = []
for p in ptds:
pattach = bytearray(p)
pather = Pather(qb64b=pattach, strip=True)
if pather.startswith(e):
np = pather.strip(e)
attachments.append((np, pattach))
kwa["attachments"] = attachments
if essrs:
kwa["essr"] = b''.join([texter.raw for texter in essrs])
if isinstance(serder.seals, str):
if 'essr' not in kwa:
raise ValidationError("at least one essr attachment is required")
essr = kwa['essr']
dig = serder.seals
diger = Diger(qb64=dig)
if not diger.verify(ser=essr):
raise ValidationError(f"essr diger={diger.qb64} is invalid against content")
# Perform behavior specific verification, think IPEX chaining requirements
try:
if not behavior.verify(serder=serder, **kwa):
logger.error("exn event for route %s failed behavior verification. said=%s", route, serder.said)
logger.debug(f"Event=\n%s\n", serder.pretty())
return
except AttributeError:
logger.debug("Behavior for %s missing or does not have verify for said %s", route, serder.said)
logger.debug("Exn Event Body=\n%s\n", serder.pretty())
# Always persist events
self.logEvent(serder, ptds, tsgs, cigars, essrs)
self.cues.append(dict(kin="saved", said=serder.said))
# Execute any behavior specific handling, not sure if this should be different than verify
try:
behavior.handle(serder=serder, **kwa)
except AttributeError:
logger.debug("Behavior for %s missing or does not have handle for SAID=%s", route, serder.said)
logger.debug("Event=\n%s\n", serder.pretty())
[docs]
def processEscrow(self):
""" Process all escrows for `exn` messages
"""
self.processEscrowPartialSigned()
[docs]
def escrowPSEvent(self, serder, tsgs, pathed):
""" Escrow event that does not have enough signatures.
Parameters:
serder (Serder): instance of event
tsgs (list): quadlet of prefixer seqner, saider, sigers
pathed (list): list of bytes of attached paths
"""
dig = serder.said
for prefixer, seqner, ssaider, sigers in tsgs: # iterate over each tsg
quadkeys = (serder.said, prefixer.qb64, f"{seqner.sn:032x}", ssaider.qb64)
for siger in sigers:
self.hby.db.esigs.add(keys=quadkeys, val=siger)
self.hby.db.epsd.put(keys=(dig,), val=Dater())
self.hby.db.epath.pin(keys=(dig,), vals=[bytes(p) for p in pathed])
return self.hby.db.epse.put(keys=(dig,), val=serder)
[docs]
def processEscrowPartialSigned(self):
""" Process escrow of partially signed messages """
for (dig,), serder in self.hby.db.epse.getTopItemIter():
try:
tsgs = []
klases = (Prefixer, Seqner, Saider)
args = ("qb64", "snh", "qb64")
sigers = []
dtnow = helping.nowUTC()
dater = self.hby.db.epsd.get(keys=(dig,))
if dater is None:
raise ValidationError("Missing exn escrowed event datetime "
f"at dig = {dig}.")
dte = dater.datetime
if (dtnow - dte) > datetime.timedelta(seconds=self.TimeoutPSE):
# escrow stale so raise ValidationError which unescrows below
raise ValidationError("Stale exn event escrow "
f"at dig = {dig}.")
old = None # empty keys
for keys, siger in self.hby.db.esigs.getTopItemIter(keys=(dig, "")):
quad = keys[1:]
if quad != old: # new tsg
if sigers: # append tsg made for old and sigers
prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args)
tsgs.append((prefixer, seqner, saider, sigers))
sigers = []
old = quad
sigers.append(siger)
if sigers and old:
prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args)
tsgs.append((prefixer, seqner, saider, sigers))
pathed = [bytearray(p.encode("utf-8")) for p in self.hby.db.epath.get(keys=(dig,))]
essrs = [texter for texter in self.hby.db.essrs.get(keys=(dig,))]
self.processEvent(serder=serder, tsgs=tsgs, ptds=pathed, essrs=essrs)
except MissingSignatureError as ex:
if logger.isEnabledFor(logging.TRACE):
logger.trace("Exchange partially signed unescrow failed: %s\n", ex.args[0])
logger.debug(f"Event body=\n%s\n", serder.pretty())
except Exception as ex:
self.hby.db.epse.rem(dig)
self.hby.db.epsd.rem(dig)
self.hby.db.esigs.rem(dig)
if logger.isEnabledFor(logging.DEBUG):
logger.exception("Exchange partially signed unescrowed: %s", ex.args[0])
else:
logger.error("Exchange partially signed unescrowed: %s", ex.args[0])
else:
self.hby.db.epse.rem(dig)
self.hby.db.esigs.rem(dig)
logger.info("Exchanger unescrow succeeded in valid exchange: creder=%s", serder.said)
logger.debug("Event=\n%s\n", serder.pretty())
def logEvent(self, serder, pathed=None, tsgs=None, cigars=None, essrs=None):
dig = serder.said
pdig = serder.ked['p']
pathed = pathed or []
tsgs = tsgs or []
cigars = cigars or []
essrs = essrs or []
for prefixer, seqner, ssaider, sigers in tsgs: # iterate over each tsg
quadkeys = (serder.said, prefixer.qb64, f"{seqner.sn:032x}", ssaider.qb64)
for siger in sigers:
self.hby.db.esigs.add(keys=quadkeys, val=siger)
for cigar in cigars:
self.hby.db.ecigs.add(keys=(dig,), val=(cigar.verfer, cigar))
diger = Diger(qb64=serder.said)
self.hby.db.epath.pin(keys=(dig,), vals=[bytes(p) for p in pathed])
for texter in essrs:
self.hby.db.essrs.add(keys=(dig,), val=texter)
if pdig:
self.hby.db.erpy.pin(keys=(pdig,), val=diger)
self.hby.db.exns.put(keys=(dig,), val=serder)
[docs]
def lead(self, hab, said):
""" Determines is current member represented by hab is the lead of an exn message
Lead is the signer of the exn with the lowest signing index
Parameters:
hab (Hab): Habitat for sending of exchange message represented by SAID
said (str): qb64 SAID of exchange message
Returns:
bool: True means hab is the lead
"""
from ..app import GroupHab
if not isinstance(hab, GroupHab):
return True
keys = [verfer.qb64 for verfer in hab.kever.verfers]
tsgs = fetchTsgs(self.hby.db.esigs, Diger(qb64=said))
if not tsgs: # otherwise it contains a list of sigs
return False
(_, _, _, sigers) = tsgs[0]
windex = min([siger.index for siger in sigers])
# True if Elected to send an EXN to its receiver
return hab.mhab.kever.verfers[0].qb64 == keys[windex]
[docs]
def complete(self, said):
"""
Args:
said (str): qb64 said of exchange message to check status
Returns:
bool: True means exchange message is has been saved
"""
serder = self.hby.db.exns.get(keys=(said,))
if not serder:
return False
else:
if serder.said != said:
raise ValidationError(f"invalid exchange escrowed event {serder.said}-{said}")
return True
[docs]
def exchangeOld(*,
sender="",
receiver="",
xid="",
prior="",
route="",
modifiers=None,
attributes=None,
diger=None,
embeds=None,
stamp=None,
version=Version,
pvrsn=None,
gvrsn=None,
kind=Kinds.json,):
""" Create an `exn` message with the specified route and payload
Parameters:
sender (str): qb64 of sender identifier (AID)
receiver (str): qb64 of receiver identifier (AID)
xid (str): qb64 of exchange ID which is SAID of exchange inception 'xip'
if any
prior (str): qb64 of prior exchange event including 'xip" if any
route (str): '/' delimited path identifier of data flow handler
(behavior) to processs the reply if any (equivalent of
url path to resource)
modifiers (dict): modifiers field map (equvalent of http query string)
attributes (dict): attributes field map (payload body)
stamp (str): date-time-stamp RFC-3339 profile of ISO-8601 datetime of
creation of message or data, default is now.
version (Versionage): KERI protocol default version if psvrsn is None
pvrsn (Versionage): KERI protocol version
gvrsn (Versionage): CESR Genus version for attachment group codes or
nesting group code (useful when serder.gvrsn < 2)
gvrsn = max(svrsn, gvrsn) where svrsn = serder.gvrsn
if serder.gvrsn else serder.pvrsn
kind (str): serialization for key event message
one of Kinds ("json","cbor","mgpk","cesr")
diger (Diger): qb64 digest of attributes section (payload)
embeds (dict): named embeded KERI event CESR stream with attachments
"""
pvrsn = pvrsn if pvrsn is not None else version
vs = versify(pvrsn=pvrsn, kind=kind, size=0, gvrsn=gvrsn)
#ilk = Ilks.exn
#dt = stamp if stamp is not None else helping.nowIso8601()
#xid = xid if xid is not None else ""
#p = prior if prior is not None else ""
#ri = receiver if receiver is not None else ""
#modifiers = modifiers if modifiers is not None else {}
end = bytearray()
if pvrsn.major == Vrsn_1_0.major:
embeds = embeds if embeds is not None else {}
e = dict()
for label, msg in embeds.items():
serder = Sadder(raw=msg)
e[label] = serder.ked
atc = bytes(msg[serder.size:])
if not atc:
continue
pathed = bytearray()
pather = Pather(parts=["e", label])
pathed.extend(pather.qb64b)
pathed.extend(atc)
if len(pathed) // 4 < 4096:
end.extend(Counter(Codens.PathedMaterialCouples,
count=(len(pathed) // 4),
version=Vrsn_1_0).qb64b)
else:
end.extend(Counter(Codens.BigPathedMaterialCouples,
count=(len(pathed) // 4),
version=Vrsn_1_0).qb64b)
end.extend(pathed)
if e:
e["d"] = ""
_, e = Saider.saidify(sad=e, label=Saids.d)
if diger is None:
#attrs = dict()
if receiver: # not (empty or None)
attributes = attributes if attributes is not None else {}
attributes['i'] = receiver
#attrs['i'] = receiver
#attrs |= attributes
else:
# only in v1 exn can the attributes field 'a' be either a said or
# a field map. In v2 it must be a field map.
attributes = diger.qb64 # SAID of ESSR encrypted attachment
sad = dict(v=vs,
t=Ilks.exn,
d="", # computed by SerderKERI init
i=sender if sender is not None else "",
rp=receiver if receiver is not None else "",
p=prior if prior is not None else "",
dt=stamp if stamp is not None else helping.nowIso8601(),
r=route if route is not None else "",
q=modifiers if modifiers is not None else {}, # q field required
a=attributes if attributes is not None else {},
e=e)
else:
if end or diger:
raise ValueError(f"Invalid diger or embeds not supported in "
f"version {pvrsn.major} exchange")
sad = dict(v=vs,
t=Ilks.exn,
d="", # computed by SerderKERI init
i=sender if sender is not None else "",
ri=receiver if receiver is not None else "",
x=xid if xid is not None else "",
p=prior if prior is not None else "",
dt=stamp if stamp is not None else helping.nowIso8601(),
r=route if route is not None else "",
q=modifiers if modifiers is not None else {}, # q field required
a=attributes if attributes is not None else {}
)
return SerderKERI(sad=sad, makify=True) # return serialized ked
#return SerderKERI(sad=sad, makify=True), end # return serialized ked
[docs]
def specialExchange(*,
sender="",
receiver="",
xid="",
prior="",
route="",
modifiers=None,
attributes=None,
diger=None,
embeds=None,
stamp=None,
version=Vrsn_1_0,
pvrsn=None,
gvrsn=None,
kind=Kinds.json,):
"""Create an `exn` with either an ESSR attachment or embeds with path
attachment as determined by the presence of diger or embeds parameters
repectively
Parameters::
sender (str): qb64 of sender identifier (AID)
receiver (str): qb64 of receiver identifier (AID)
xid (str): qb64 of exchange ID which is SAID of exchange inception 'xip'
if any
prior (str): qb64 of prior exchange event including 'xip" if any
route (str): '/' delimited path identifier of data flow handler
(behavior) to processs the reply if any (equivalent of
url path to resource)
modifiers (dict): modifiers field map (equvalent of http query string)
attributes (dict): attributes field map (payload body)
stamp (str): date-time-stamp RFC-3339 profile of ISO-8601 datetime of
creation of message or data, default is now.
version (Versionage): KERI protocol default version if psvrsn is None
pvrsn (Versionage): KERI protocol version
gvrsn (Versionage): CESR Genus version for attachment group codes or
nesting group code (useful when serder.gvrsn < 2)
gvrsn = max(svrsn, gvrsn) where svrsn = serder.gvrsn
if serder.gvrsn else serder.pvrsn
kind (str): serialization for key event message
one of Kinds ("json","cbor","mgpk","cesr")
diger (Diger): qb64 digest of attributes section (payload)
embeds (dict): named embeded KERI event CESR stream with attachments
Returns::
embedded (SerderKeri, bytearray): of form (exchange, attachments) where
exchange is serder of exchange message and atc is serialized path
attachments of embeds
"""
pvrsn = pvrsn if pvrsn is not None else version
vs = versify(pvrsn=pvrsn, kind=kind, size=0, gvrsn=gvrsn)
#ilk = Ilks.exn
#dt = stamp if stamp is not None else helping.nowIso8601()
#xid = xid if xid is not None else ""
#p = prior if prior is not None else ""
#ri = receiver if receiver is not None else ""
#modifiers = modifiers if modifiers is not None else {}
if pvrsn.major == Vrsn_1_0.major:
end = bytearray()
embeds = embeds if embeds is not None else {}
e = dict()
for label, msg in embeds.items():
serder = Sadder(raw=msg)
e[label] = serder.ked
atc = bytes(msg[serder.size:])
if not atc:
continue
pathed = bytearray()
pather = Pather(parts=["e", label])
pathed.extend(pather.qb64b)
pathed.extend(atc)
if len(pathed) // 4 < 4096:
end.extend(Counter(Codens.PathedMaterialCouples,
count=(len(pathed) // 4),
version=Vrsn_1_0).qb64b)
else:
end.extend(Counter(Codens.BigPathedMaterialCouples,
count=(len(pathed) // 4),
version=Vrsn_1_0).qb64b)
end.extend(pathed)
if e:
e["d"] = ""
_, e = Saider.saidify(sad=e, label=Saids.d)
if diger is None:
if receiver: # not (empty or None)
attributes = attributes if attributes is not None else {}
attributes['i'] = receiver
else:
# only in v1 exn can the attributes field 'a' be either a said or
# a field map. In v2 it must be a field map.
attributes = diger.qb64 # SAID of ESSR encrypted attachment
sad = dict(v=vs,
t=Ilks.exn,
d="", # computed by SerderKERI init
i=sender if sender is not None else "",
rp=receiver if receiver is not None else "",
p=prior if prior is not None else "",
dt=stamp if stamp is not None else helping.nowIso8601(),
r=route if route is not None else "",
q=modifiers if modifiers is not None else {}, # q field required
a=attributes if attributes is not None else {},
e=e)
else:
raise ValueError(f"Invalid specialExchange not supported in version"
f" {pvrsn.major} exchange")
return SerderKERI(sad=sad, makify=True), end # return serialized ked
[docs]
def cloneMessage(hby, said):
""" Load and verify signatures on message exn
Parameters:
hby (Habery): database environment from which to clone message
said (str): qb64 SAID of message exn to load
Returns:
tuple: (serder, list) of message exn and pathed signatures on embedded attachments
"""
exn = hby.db.exns.get(keys=(said,))
if exn is None:
return None, None
verify(hby=hby, serder=exn)
pathed = dict()
e = Pather(parts=["e"])
for p in hby.db.epath.get(keys=(exn.said,)):
pb = bytearray(p.encode("utf-8"))
pather = Pather(qb64b=pb, strip=True)
if pather.startswith(e):
np = pather.strip(e)
nesting(np.rparts, pathed, pb) # no unit test for this
return exn, pathed
[docs]
def serializeMessage(hby, said, framed=False):
"""Fetch message and attachments from hby.db by said and then serialize them
Parameters::
hby (Habery): environment with db
said (str): of message
framed (bool): True means may assume each message plus its attachments
is isolated as frame when parsing so do not need
attachment group when messagizing
False means may not assume eash message plus its attachments
is isolated as frame when parsing so do need
attachment group when messagizing
Returns::
msg (bytearray): message by said with attachments
"""
atc = bytearray()
exn = hby.db.exns.get(keys=(said,))
if exn is None:
return None, None
atc.extend(exn.raw)
tsgs, cigars = verify(hby=hby, serder=exn)
if len(tsgs) > 0:
for (prefixer, seqner, saider, sigers) in tsgs:
atc.extend(Counter(Codens.TransIdxSigGroups, count=1,
version=Vrsn_1_0).qb64b)
atc.extend(prefixer.qb64b)
atc.extend(seqner.qb64b)
atc.extend(saider.qb64b)
atc.extend(Counter(Codens.ControllerIdxSigs, count=len(sigers),
version=Vrsn_1_0).qb64b)
for siger in sigers:
atc.extend(siger.qb64b)
if len(cigars) > 0:
atc.extend(Counter(Codens.NonTransReceiptCouples,
count=len(cigars), version=Vrsn_1_0).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)
# Smash the pathed components on the end
for p in hby.db.epath.get(keys=(exn.said,)):
atc.extend(Counter(Codens.PathedMaterialCouples,
count=(len(p) // 4), version=Vrsn_1_0).qb64b)
atc.extend(p.encode("utf-8"))
msg = bytearray()
if len(atc) % 4:
raise ValueError("Invalid attachments size={}, nonintegral"
" quadlets.".format(len(atc)))
if not framed:
msg.extend(Counter(Codens.AttachmentGroup,
count=(len(atc) // 4), version=Vrsn_1_0).qb64b)
msg.extend(atc)
return msg
[docs]
def nesting(paths, acc, val):
"""Nesting Pather parts
Parameters:
paths (list[list]): list of path parts
"""
if len(paths) == 0:
return val
else:
first_value = paths[0]
nacc = dict()
acc[first_value] = nesting(paths[1:], nacc, val)
return acc
[docs]
def verify(hby, serder):
""" Verify that the signatures in the database are valid for the provided exn
Parameters:
hby (Habery): database environment from which to verify message
serder (Serder): exn serder to load and verify signatures for
Returns:
bool: True means threshold satisfyig signatures were loaded and verified successfully
"""
tsgs = []
klases = (Prefixer, Seqner, Saider)
args = ("qb64", "snh", "qb64")
sigers = []
old = None # empty keys
for keys, siger in hby.db.esigs.getTopItemIter(keys=(serder.said, "")):
quad = keys[1:]
if quad != old: # new tsg
if sigers: # append tsg made for old and sigers
prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args)
tsgs.append((prefixer, seqner, saider, sigers))
sigers = []
old = quad
sigers.append(siger)
if sigers and old:
prefixer, seqner, saider = helping.klasify(sers=old, klases=klases, args=args)
tsgs.append((prefixer, seqner, saider, sigers))
accepted = False
for prefixer, seqner, ssaider, sigers in tsgs:
if prefixer.qb64 not in hby.kevers or hby.kevers[prefixer.qb64].sn < seqner.sn:
msg = f"Unable to find sender {prefixer.qb64} in kevers for evt = {serder.said}"
logger.info(msg)
logger.debug("Exn Body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
# Verify the signatures are valid and that the signature threshold as of the signing event is met
tholder, verfers = hby.db.resolveVerifiers(pre=prefixer.qb64, sn=seqner.sn, dig=ssaider.qb64)
_, indices = verifySigs(serder.raw, sigers, verfers)
if not tholder.satisfy(indices): # We still don't have all the sigers, need to escrow
msg = f"Not enough signatures in idx={indices} for evt = {serder.said}"
logger.info(msg)
logger.debug("Exn Body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
accepted = True
cigars = hby.db.ecigs.get(keys=(serder.said,))
for cigar in cigars:
if not cigar.verfer.verify(cigar.raw, serder.raw): # cig not verify
msg = f"Failure satisfying exn on cigs for {cigar} for evt = {serder.said}"
logger.info(msg)
logger.debug("Exn Body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
accepted = True
if not accepted:
msg = f"No valid signatures stored for evt = {serder.said}"
logger.info(msg)
logger.debug("Exn Body=\n%s\n", serder.pretty())
raise MissingSignatureError(msg)
return tsgs, cigars