# -*- encoding: utf-8 -*-
"""
KERIA
keria.app.credentialing module
services and endpoint for ACDC credential managements
"""
import json
from dataclasses import asdict
import falcon
from keri import kering
from keri.app import signing
from keri.app.habbing import SignifyGroupHab
from keri.core import coring, scheming, serdering
from keri.db import dbing
from keri.db.dbing import dgKey
from keri.vdr import viring
from keria.core import httping, longrunning
def loadEnds(app, identifierResource):
schemaColEnd = SchemaCollectionEnd()
app.add_route("/schema", schemaColEnd)
schemaResEnd = SchemaResourceEnd()
app.add_route("/schema/{said}", schemaResEnd)
registryEnd = RegistryCollectionEnd(identifierResource)
app.add_route("/identifiers/{name}/registries", registryEnd)
registryResEnd = RegistryResourceEnd()
app.add_route("/identifiers/{name}/registries/{registryName}", registryResEnd)
credentialCollectionEnd = CredentialCollectionEnd(identifierResource)
app.add_route("/identifiers/{name}/credentials", credentialCollectionEnd)
credentialResourceEnd = CredentialResourceEnd()
app.add_route("/credentials/{said}", credentialResourceEnd)
credentialResourceDelEnd = CredentialResourceDeleteEnd(identifierResource)
app.add_route("/identifiers/{name}/credentials/{said}", credentialResourceDelEnd)
queryCollectionEnd = CredentialQueryCollectionEnd()
app.add_route("/credentials/query", queryCollectionEnd)
[docs]
class RegistryCollectionEnd:
"""
ReST API for admin of credential issuance and revocation registries
"""
def __init__(self, identifierResource):
"""
Parameters:
identifierResource (IdentifierResourceEnd): endpoint class for creating rotation and interaction events
"""
self.identifierResource = identifierResource
[docs]
@staticmethod
def on_get(req, rep, name):
""" Registries GET endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
name (str): human readable name for AID
---
summary: List credential issuance and revocation registies
description: List credential issuance and revocation registies
tags:
- Registries
responses:
200:
description: array of current credential issuance and revocation registies
"""
agent = req.context.agent
hab = agent.hby.habByName(name)
if hab is None:
raise falcon.HTTPNotFound(description="name is not a valid reference to an identifier")
res = []
for name, registry in agent.rgy.regs.items():
if registry.regk not in registry.tevers: # defensive programming for a registry not being fully committed
continue
if registry.hab.pre == hab.pre:
rd = dict(
name=registry.name,
regk=registry.regk,
pre=registry.hab.pre,
state=asdict(registry.tever.state())
)
res.append(rd)
rep.status = falcon.HTTP_200
rep.content_type = "application/json"
rep.data = json.dumps(res).encode("utf-8")
[docs]
def on_post(self, req, rep, name):
""" Registries POST endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
name (str): AID of Hab to load credentials for
---
summary: Request to create a credential issuance and revocation registry
description: Request to create a credential issuance and revocation registry
tags:
- Registries
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
description: name of the new registry
alias:
type: string
description: name of identifier to associate as the issuer of the new credential registry
toad:
type: integer
description: Backer receipt threshold
nonce:
type: string
description: qb64 encoded ed25519 random seed for registry
noBackers:
type: boolean
required: False
description: True means to not allow seperate backers from identifier's witnesses.
baks:
type: array
items:
type: string
description: List of qb64 AIDs of witnesses to be used for the new group identifier.
estOnly:
type: boolean
required: false
default: false
description: True means to not allow interaction events to anchor credential events.
responses:
202:
description: registry inception request has been submitted
"""
agent = req.context.agent
body = req.get_media()
rname = httping.getRequiredParam(body, "name")
ked = httping.getRequiredParam(body, "vcp")
vcp = serdering.SerderKERI(sad=ked)
ked = httping.getRequiredParam(body, "ixn")
ixn = serdering.SerderKERI(sad=ked)
hab = agent.hby.habByName(name)
if hab is None:
raise falcon.HTTPNotFound(description="alias is not a valid reference to an identifier")
registry = agent.rgy.makeSignifyRegistry(name=rname, prefix=hab.pre, regser=vcp)
if hab.kever.estOnly:
op = self.identifierResource.rotate(agent, name, body)
else:
op = self.identifierResource.interact(agent, name, body)
anchor = dict(i=registry.regk, s="0", d=registry.regk)
# Create registry long running OP that embeds the above received OP or Serder.
seqner = coring.Seqner(sn=ixn.sn)
prefixer = coring.Prefixer(qb64=ixn.pre)
agent.registrar.incept(hab, registry, prefixer=prefixer, seqner=seqner, saider=coring.Saider(qb64=ixn.said))
op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.registry,
metadata=dict(anchor=anchor, depends=op))
rep.status = falcon.HTTP_202
rep.data = op.to_json().encode("utf-8")
class RegistryResourceEnd:
@staticmethod
def on_get(req, rep, name, registryName):
""" Registry Resource GET endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
name (str): human readable name for AID
registryName(str): human readable name for registry
---
summary: Get a single credential issuance and revocation registy
description: Get a single credential issuance and revocation registy
tags:
- Registries
responses:
200:
description: credential issuance and revocation registy
"""
agent = req.context.agent
hab = agent.hby.habByName(name)
if hab is None:
raise falcon.HTTPNotFound(description=f"{name} is not a valid reference to an identifier")
registry = agent.rgy.registryByName(registryName)
if registry is None:
raise falcon.HTTPNotFound(description=f"{registryName} is not a valid reference to a credential registry")
if not registry.hab.pre == hab.pre:
raise falcon.HTTPNotFound(description=f"{registryName} is not a valid registry for AID {name}")
rd = dict(
name=registry.name,
regk=registry.regk,
pre=registry.hab.pre,
state=asdict(registry.tever.state())
)
rep.status = falcon.HTTP_200
rep.content_type = "application/json"
rep.data = json.dumps(rd).encode("utf-8")
@staticmethod
def on_put(req, rep, name, registryName):
""" Registry Resource PUT endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
name (str): human readable name for AID
registryName(str): human readable name for registry or its SAID
---
summary: Get a single credential issuance and revocation registy
description: Get a single credential issuance and revocation registy
tags:
- Registries
responses:
200:
description: credential issuance and revocation registy
"""
agent = req.context.agent
hab = agent.hby.habByName(name)
if hab is None:
raise falcon.HTTPNotFound(description=f"{name} is not a valid reference to an identifier")
body = req.get_media()
if 'name' not in body:
raise falcon.HTTPBadRequest(description="'name' is required in body")
name = body['name']
if agent.rgy.registryByName(name) is not None:
raise falcon.HTTPBadRequest(description=f"{name} is already in use for a registry")
registry = agent.rgy.registryByName(registryName)
if registry is None:
if registryName in agent.rgy.regs: # Check to see if the registryName parameter is a SAID
registry = agent.rgy.regs[registryName]
else:
regk = registryName
key = dgKey(regk, regk)
raw = agent.rgy.reger.getTvt(key=key)
if raw is None:
raise falcon.HTTPNotFound(
description=f"{registryName} is not a valid reference to a credential registry")
regser = serdering.SerderKERI(raw=bytes(raw))
registry = agent.rgy.makeSignifyRegistry(name, hab.pre, regser)
regord = viring.RegistryRecord(registryKey=registry.regk, prefix=hab.pre)
agent.rgy.reger.regs.pin(keys=(name,), val=regord)
registry.name = name
rd = dict(
name=registry.name,
regk=registry.regk,
pre=registry.hab.pre,
state=asdict(registry.tever.state())
)
rep.status = falcon.HTTP_200
rep.content_type = "application/json"
rep.data = json.dumps(rd).encode("utf-8")
class SchemaResourceEnd:
@staticmethod
def on_get(req, rep, said):
""" Schema GET endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
said: qb64 self-addressing identifier of schema to load
---
summary: Get schema JSON of specified schema
description: Get schema JSON of specified schema
tags:
- Schema
parameters:
- in: path
name: said
schema:
type: string
required: true
description: qb64 self-addressing identifier of schema to get
responses:
200:
description: Schema JSON successfully returned
404:
description: No schema found for SAID
"""
agent = req.context.agent
schemer = agent.hby.db.schema.get(keys=(said,))
if schemer is None:
raise falcon.HTTPNotFound(description="Schema not found")
data = schemer.sed
rep.status = falcon.HTTP_200
rep.data = json.dumps(data).encode("utf-8")
class SchemaCollectionEnd:
@staticmethod
def on_get(req, rep):
""" Schema GET plural endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
---
summary: Get schema JSON of all schema
description: Get schema JSON of all schema
tags:
- Schema
responses:
200:
description: Array of all schema JSON
"""
agent = req.context.agent
data = []
for said, schemer in agent.hby.db.schema.getItemIter():
data.append(schemer.sed)
rep.status = falcon.HTTP_200
rep.data = json.dumps(data).encode("utf-8")
[docs]
class CredentialQueryCollectionEnd:
""" This class provides a collection endpoint for creating credential queries.
I fully admit that the semantics here are a big stretch. I would rather have this as a GET against the
credential collection endpoint, but the nature of the complicated input to this endpoint dictate a BODY
and certain client libraries (and possibly reverse proxies) don't support a BODY in a GET request. So
I'm moving the credential query code to this endpoint class and mapping to `.../credentials/queries` and
making it a post against that path and calling it "creating a creaential query". Meh.
"""
[docs]
@staticmethod
def on_post(req, rep):
""" Credentials GET endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
---
summary: List credentials in credential store (wallet)
description: List issued or received credentials current verified
tags:
- Credentials
parameters:
- in: path
name: aid
schema:
type: string
required: true
description: identifier to load credentials for
- in: query
name: type
schema:
type: string
description: type of credential to return, [issued|received]
required: true
- in: query
name: schema
schema:
type: string
description: schema to filter by if provided
required: false
responses:
200:
description: Credential list.
content:
application/json:
schema:
description: Credentials
type: array
items:
type: object
"""
agent = req.context.agent
try:
body = req.get_media()
if "filter" in body:
filtr = body["filter"]
else:
filtr = {}
if "sort" in body:
sort = body["sort"]
else:
sort = None
if "skip" in body:
skip = body["skip"]
else:
skip = 0
if "limit" in body:
limit = body["limit"]
else:
limit = 25
except falcon.HTTPError:
filtr = {}
sort = {}
skip = 0
limit = 25
cur = agent.seeker.find(filtr=filtr, sort=sort, skip=skip, limit=limit)
saids = [coring.Saider(qb64=said) for said in cur]
creds = agent.rgy.reger.cloneCreds(saids=saids, db=agent.hby.db)
end = skip + (len(creds) - 1) if len(creds) > 0 else 0
rep.set_header("Accept-Ranges", "credentials")
rep.set_header("Content-Range", f"credentials {skip}-{end}/{limit}")
rep.status = falcon.HTTP_200
rep.content_type = "application/json"
rep.data = json.dumps(creds).encode("utf-8")
class CredentialCollectionEnd:
def __init__(self, identifierResource):
"""
Parameters:
identifierResource (IdentifierResourceEnd): endpoint class for creating rotation and interaction events
"""
self.identifierResource = identifierResource
def on_post(self, req, rep, name):
""" Initiate a credential issuance
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
name (str): human readable alias for AID to use as issuer
---
summary: Perform credential issuance
description: Perform credential issuance
tags:
- Credentials
parameters:
- in: path
name: alias
schema:
type: string
required: true
description: Human readable alias for the identifier to create
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
registry:
type: string
description: Alias of credential issuance/revocation registry (aka status)
recipient:
type: string
description: AID of credential issuance/revocation recipient
schema:
type: string
description: SAID of credential schema being issued
rules:
type: object
description: Rules section (Ricardian contract) for credential being issued
source:
type: object
description: ACDC edge or edge group for chained credentials
properties:
d:
type: string
description: SAID of reference chain
s:
type: string
description: SAID of reference chain schema
credentialData:
type: object
description: dynamic map of values specific to the schema
private:
type: boolean
description: flag to inidicate this credential should support privacy preserving presentations
responses:
200:
description: Credential issued.
content:
application/json:
schema:
description: Credential
type: object
"""
agent = req.context.agent
body = req.get_media()
hab = agent.hby.habByName(name)
if hab is None:
raise falcon.HTTPNotFound(description="name is not a valid reference to an identifier")
creder = serdering.SerderACDC(sad=httping.getRequiredParam(body, "acdc"))
iserder = serdering.SerderKERI(sad=httping.getRequiredParam(body, "iss"))
if "ixn" in body:
anc = serdering.SerderKERI(sad=httping.getRequiredParam(body, "ixn"))
else:
anc = serdering.SerderKERI(sad=httping.getRequiredParam(body, "rot"))
regk = iserder.ked['ri']
if regk not in agent.rgy.tevers:
raise falcon.HTTPNotFound(description=f"issue against invalid registry SAID {regk}")
if hab.kever.estOnly:
op = self.identifierResource.rotate(agent, name, body)
else:
op = self.identifierResource.interact(agent, name, body)
try:
agent.credentialer.validate(creder)
agent.registrar.issue(regk, iserder, anc)
agent.credentialer.issue(creder=creder, serder=iserder)
op = agent.monitor.submit(hab.kever.prefixer.qb64, longrunning.OpTypes.credential,
metadata=dict(ced=creder.sad, depends=op))
except kering.ConfigurationError as e:
rep.status = falcon.HTTP_400
rep.text = e.args[0]
return
rep.status = falcon.HTTP_200
rep.data = op.to_json().encode("utf-8")
class CredentialResourceEnd:
def __init__(self):
"""
"""
@staticmethod
def on_get(req, rep, said):
""" Credentials GET endpoint
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
said (str): SAID of credential to export
---
summary: Export credential and all supporting cryptographic material
description: Export credential and all supporting cryptographic material
tags:
- Credentials
parameters:
- in: path
name: aid
schema:
type: string
required: true
description: The identifier to create
- in: path
name: said
schema:
type: string
required: true
description: SAID of credential to get
responses:
200:
description: Credential export.
content:
application/json+cesr:
schema:
description: Credential
type: object
"""
agent = req.context.agent
accept = req.get_header("accept")
if accept == "application/json+cesr":
rep.content_type = "application/json+cesr"
data = CredentialResourceEnd.outputCred(agent.hby, agent.rgy, said)
else:
rep.content_type = "application/json"
creds = agent.rgy.reger.cloneCreds([coring.Saider(qb64=said)], db=agent.hby.db)
if not creds:
raise falcon.HTTPNotFound(description=f"credential for said {said} not found.")
data = json.dumps(creds[0]).encode("utf-8")
rep.status = falcon.HTTP_200
rep.data = bytes(data)
@staticmethod
def outputCred(hby, rgy, said):
out = bytearray()
creder, prefixer, seqner, saider = rgy.reger.cloneCred(said=said)
chains = creder.edge or dict()
saids = []
for key, source in chains.items():
if key == 'd':
continue
if not isinstance(source, dict):
continue
saids.append(source['n'])
for said in saids:
out.extend(CredentialResourceEnd.outputCred(hby, rgy, said))
issr = creder.issuer
for msg in hby.db.clonePreIter(pre=issr):
serder = serdering.SerderKERI(raw=msg)
atc = msg[serder.size:]
out.extend(serder.raw)
out.extend(atc)
if "i" in creder.attrib:
subj = creder.attrib["i"]
for msg in hby.db.clonePreIter(pre=subj):
serder = serdering.SerderKERI(raw=msg)
atc = msg[serder.size:]
out.extend(serder.raw)
out.extend(atc)
if creder.regi is not None:
for msg in rgy.reger.clonePreIter(pre=creder.regi):
serder = serdering.SerderKERI(raw=msg)
atc = msg[serder.size:]
out.extend(serder.raw)
out.extend(atc)
for msg in rgy.reger.clonePreIter(pre=creder.said):
serder = serdering.SerderKERI(raw=msg)
atc = msg[serder.size:]
out.extend(serder.raw)
out.extend(atc)
out.extend(signing.serialize(creder, prefixer, seqner, saider))
return out
class CredentialResourceDeleteEnd:
def __init__(self, identifierResource):
"""
Parameters:
identifierResource (IdentifierResourceEnd): endpoint class for creating rotation and interaction events
"""
self.identifierResource = identifierResource
def on_delete(self, req, rep, name, said):
""" Initiate a credential revocation
Parameters:
req: falcon.Request HTTP request
rep: falcon.Response HTTP response
name (str): human readable alias for AID to use as issuer
said (str): SAID of credential to revoke
RequestBody:
rev (str): serialized revocation event
ixn (str): serialized interaction event
rot (str): serialized rotation event
sigs (list): list of signatures for the revocation event
---
summary: Perform credential revocation
description: Perform credential revocation
"""
agent = req.context.agent
body = req.get_media()
hab = agent.hby.habByName(name)
if hab is None:
raise falcon.HTTPNotFound(description="name is not a valid reference to an identifier")
rserder = serdering.SerderKERI(sad=httping.getRequiredParam(body, "rev"))
regk = rserder.ked['ri']
if regk not in agent.rgy.tevers:
raise falcon.HTTPNotFound(description=f"revocation against invalid registry SAID {regk}")
try:
agent.rgy.reger.cloneCreds([coring.Saider(qb64=said)], db=agent.hby.db)
except:
raise falcon.HTTPNotFound(description=f"credential for said {said} not found.")
if hab.kever.estOnly:
op = self.identifierResource.rotate(agent, name, body)
anc = httping.getRequiredParam(body, "rot")
else:
op = self.identifierResource.interact(agent, name, body)
anc = httping.getRequiredParam(body, "ixn")
try:
agent.registrar.revoke(regk, rserder, anc)
except Exception as e:
raise falcon.HTTPBadRequest(description=f"invalid revocation event.")
rep.status = falcon.HTTP_200
rep.data = op.to_json().encode("utf-8")
[docs]
def signPaths(hab, pather, sigers):
""" Sign the SAD or SAIDs with the keys from the Habitat.
Sign the SADs or SAIDs of the SADs as identified by the paths.
Parameters:
hab (Habitat): environment used to sign the SAD
pather (Pather): Pather for the signatures
sigers (list): list of signatures over the paths
Returns:
list: pathed signature tuples
"""
sadsigers = []
prefixer, seqner, saider, indices = signing.transSeal(hab)
sadsigers.append((pather, prefixer, seqner, saider, sigers))
return sadsigers
class Registrar:
def __init__(self, agentHab, hby, rgy, counselor, witDoer, witPub, verifier):
self.hby = hby
self.agentHab = agentHab
self.rgy = rgy
self.counselor = counselor
self.witDoer = witDoer
self.witPub = witPub
self.verifier = verifier
def incept(self, hab, registry, prefixer=None, seqner=None, saider=None):
"""
Parameters:
hab (Hab): human readable name for the registry
registry (SignifyRegistry): qb64 identifier prefix of issuing identifier in control of this registry
prefixer (Prefixer):
seqner (Seqner): sequence number class of anchoring event
saider (Saider): SAID class of anchoring event
Returns:
Registry: created registry
"""
rseq = coring.Seqner(sn=0)
if not isinstance(hab, SignifyGroupHab):
seqner = coring.Seqner(sn=hab.kever.sner.num)
saider = coring.Saider(qb64=hab.kever.serder.said)
registry.anchorMsg(pre=registry.regk, regd=registry.regd, seqner=seqner, saider=saider)
self.witDoer.msgs.append(dict(pre=hab.pre, sn=seqner.sn))
self.rgy.reger.tpwe.add(keys=(registry.regk, rseq.qb64), val=(hab.kever.prefixer, seqner, saider))
else:
print("Waiting for TEL registry vcp event mulisig anchoring event")
self.rgy.reger.tmse.add(keys=(registry.regk, rseq.qb64, registry.regd), val=(prefixer, seqner, saider))
def issue(self, regk, iserder, anc):
"""
Create and process the credential issuance TEL events on the given registry
Parameters:
regk (str): qb64 identifier prefix of the credential registry
iserder (Serder): TEL issuance event
anc (Serder): Anchoring KEL event
"""
registry = self.rgy.regs[regk]
registry.processEvent(serder=iserder)
hab = registry.hab
vcid = iserder.ked["i"]
rseq = coring.Seqner(snh=iserder.ked["s"])
if not isinstance(hab, SignifyGroupHab): # not a multisig group
seqner = coring.Seqner(sn=hab.kever.sner.num)
saider = coring.Saider(qb64=hab.kever.serder.said)
registry.anchorMsg(pre=vcid, regd=iserder.said, seqner=seqner, saider=saider)
print("Waiting for TEL event witness receipts")
self.witDoer.msgs.append(dict(pre=hab.pre, sn=seqner.sn))
self.rgy.reger.tpwe.add(keys=(vcid, rseq.qb64), val=(hab.kever.prefixer, seqner, saider))
return vcid, rseq.sn
else: # multisig group hab
sn = anc.sn
said = anc.said
prefixer = coring.Prefixer(qb64=hab.pre)
seqner = coring.Seqner(sn=sn)
saider = coring.Saider(qb64=said)
print(f"Waiting for TEL iss event mulisig anchoring event {seqner.sn}")
self.rgy.reger.tmse.add(keys=(vcid, rseq.qb64, iserder.said), val=(prefixer, seqner, saider))
return vcid, rseq.sn
def revoke(self, regk, rserder, anc):
"""
Create and process the credential revocation TEL events on the given registry
Parameters:
regk (str): qb64 identifier prefix of the credential registry
rserder (Serder): TEL revocation event
anc (Serder): KEL anchoring event
"""
registry = self.rgy.regs[regk]
registry.processEvent(serder=rserder)
hab = registry.hab
vcid = rserder.ked["i"]
rseq = coring.Seqner(snh=rserder.ked["s"])
if not isinstance(hab, SignifyGroupHab):
seqner = coring.Seqner(sn=hab.kever.sner.num)
saider = coring.Saider(qb64=hab.kever.serder.said)
registry.anchorMsg(pre=vcid, regd=rserder.said, seqner=seqner, saider=saider)
print("Waiting for TEL event witness receipts")
self.witDoer.msgs.append(dict(pre=hab.pre, sn=seqner.sn))
self.rgy.reger.tpwe.add(keys=(vcid, rseq.qb64), val=(hab.kever.prefixer, seqner, saider))
return vcid, rseq.sn
else:
serder = serdering.SerderKERI(sad=anc)
sn = serder.sn
said = serder.said
prefixer = coring.Prefixer(qb64=hab.pre)
seqner = coring.Seqner(sn=sn)
saider = coring.Saider(qb64=said)
self.counselor.start(prefixer=prefixer, seqner=seqner, saider=saider, ghab=hab)
print(f"Waiting for TEL rev event mulisig anchoring event {seqner.sn}")
self.rgy.reger.tmse.add(keys=(vcid, rseq.qb64, rserder.said), val=(prefixer, seqner, saider))
return vcid, rseq.sn
def complete(self, pre, sn=0):
""" Determine if registry event (inception, issuance, revocation, etc.) is finished validation
Parameters:
pre (str): qb64 identifier of registry event
sn (int): integer sequence number of regsitry event
Returns:
bool: True means event has completed and is commited to database
"""
seqner = coring.Seqner(sn=sn)
said = self.rgy.reger.ctel.get(keys=(pre, seqner.qb64))
return said is not None
def processEscrows(self):
"""
Process credential registry anchors:
"""
self.processWitnessEscrow()
self.processMultisigEscrow()
self.processDiseminationEscrow()
def processWitnessEscrow(self):
"""
Process escrow of group multisig events that do not have a full compliment of receipts
from witnesses yet. When receipting is complete, remove from escrow and cue up a message
that the event is complete.
"""
for (regk, snq), (prefixer, seqner, saider) in self.rgy.reger.tpwe.getItemIter(): # partial witness escrow
kever = self.hby.kevers[prefixer.qb64]
dgkey = dbing.dgKey(prefixer.qb64b, saider.qb64)
# Load all the witness receipts we have so far
wigs = self.hby.db.getWigs(dgkey)
if kever.wits:
if len(wigs) == len(kever.wits): # We have all of them, this event is finished
hab = self.hby.habs[prefixer.qb64]
witnessed = False
for cue in self.witDoer.cues:
if cue["pre"] == hab.pre and cue["sn"] == seqner.sn:
witnessed = True
if not witnessed:
continue
else:
continue
rseq = coring.Seqner(qb64=snq)
self.rgy.reger.tpwe.rem(keys=(regk, snq))
self.rgy.reger.tede.add(keys=(regk, rseq.qb64), val=(prefixer, seqner, saider))
def processMultisigEscrow(self):
"""
Process escrow of group multisig events that do not have a full compliment of receipts
from witnesses yet. When receipting is complete, remove from escrow and cue up a message
that the event is complete.
"""
for (regk, snq, regd), (prefixer, seqner, saider) in self.rgy.reger.tmse.getItemIter(): # multisig escrow
try:
if not self.counselor.complete(prefixer, seqner, saider):
continue
except kering.ValidationError:
self.rgy.reger.tmse.rem(keys=(regk, snq, regd))
continue
rseq = coring.Seqner(qb64=snq)
# Anchor the message, registry or otherwise
key = dbing.dgKey(regk, regd)
sealet = seqner.qb64b + saider.qb64b
self.rgy.reger.putAnc(key, sealet)
self.rgy.reger.tmse.rem(keys=(regk, snq, regd))
self.rgy.reger.tede.add(keys=(regk, rseq.qb64), val=(prefixer, seqner, saider))
def processDiseminationEscrow(self):
for (regk, snq), (prefixer, seqner, saider) in self.rgy.reger.tede.getItemIter(): # group multisig escrow
rseq = coring.Seqner(qb64=snq)
dig = self.rgy.reger.getTel(key=dbing.snKey(pre=regk, sn=rseq.sn))
if dig is None:
continue
self.rgy.reger.tede.rem(keys=(regk, snq))
tevt = bytearray()
for msg in self.rgy.reger.clonePreIter(pre=regk, fn=rseq.sn):
tevt.extend(msg)
print(f"Sending TEL events to witnesses")
# Fire and forget the TEL event to the witnesses. Consumers will have to query
# to determine when the Witnesses have received the TEL events.
self.witPub.msgs.append(dict(pre=prefixer.qb64, msg=tevt))
self.rgy.reger.ctel.put(keys=(regk, rseq.qb64), val=saider) # idempotent
class Credentialer:
def __init__(self, agentHab, hby, rgy, registrar, verifier, notifier):
self.agentHab = agentHab
self.hby = hby
self.rgy = rgy
self.registrar = registrar
self.verifier = verifier
self.notifier = notifier
def validate(self, creder):
"""
Args:
creder (Creder): creder object representing the credential to validate
Returns:
bool: true if credential is valid against a known schema
"""
schema = creder.sad['s']
scraw = self.verifier.resolver.resolve(schema)
if not scraw:
raise kering.ConfigurationError("Credential schema {} not found. It must be loaded with data oobi before "
"issuing credentials".format(schema))
schemer = scheming.Schemer(raw=scraw)
try:
schemer.verify(creder.raw)
except kering.ValidationError as ex:
raise kering.ConfigurationError(f"Credential schema validation failed for {schema}: {ex}")
return True
def issue(self, creder, serder):
""" Issue the credential creder and handle witness propagation and communication
Parameters:
creder (Creder): Credential object to issue
serder (Serder): KEL or TEL anchoring event
"""
prefixer = coring.Prefixer(qb64=serder.pre)
seqner = coring.Seqner(sn=serder.sn)
self.rgy.reger.cmse.put(keys=(creder.said, seqner.qb64), val=creder)
try:
self.verifier.processCredential(creder=creder, prefixer=prefixer, seqner=seqner,
saider=coring.Saider(qb64=serder.said))
except kering.MissingRegistryError:
pass
def processCredentialMissingSigEscrow(self):
for (said, snq), creder in self.rgy.reger.cmse.getItemIter():
rseq = coring.Seqner(qb64=snq)
if not self.registrar.complete(pre=said, sn=rseq.sn):
continue
saider = self.rgy.reger.saved.get(keys=said)
if saider is None:
continue
# Remove from this escrow
self.rgy.reger.cmse.rem(keys=(said, snq))
# place in escrow to diseminate to other if witnesser and if there is an issuee
self.rgy.reger.ccrd.put(keys=(creder.said,), val=creder)
def complete(self, said):
return self.rgy.reger.ccrd.get(keys=(said,)) is not None
def processEscrows(self):
"""
Process credential registry anchors:
"""
self.processCredentialMissingSigEscrow()