Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@
'cbor2>=5.6.2',
'multidict>=6.0.5',
'ordered-set>=4.1.0',
'hio>=0.6.14',
'hio==0.6.14',
'multicommand>=1.0.0',
'jsonschema>=4.21.1',
'falcon>=3.1.3',
Expand Down
18 changes: 6 additions & 12 deletions src/keri/app/cli/commands/delegate/confirm.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,12 +121,10 @@ def confirmDo(self, tymth, tock=0.0):
if ilk in (coring.Ilks.dip,):
typ = "inception"
delpre = eserder.sad["di"]

elif ilk in (coring.Ilks.drt,):
typ = "rotation"
dkever = self.hby.kevers[eserder.pre]
delpre = dkever.delpre

else:
continue

Expand Down Expand Up @@ -170,11 +168,11 @@ def confirmDo(self, tymth, tock=0.0):
saider = self.hby.db.cgms.get(keys=(prefixer.qb64, sner.qb64))
if saider is not None:
break

yield self.tock

print(f"Delegate {eserder.pre} {typ} event committed.")
print(f"Delegate {typ} event {eserder.pre} committed.")

self.hby.db.delegables.rem(keys=(pre, sn), val=edig)
self.remove(self.toRemove)
return True

Expand Down Expand Up @@ -211,27 +209,23 @@ def confirmDo(self, tymth, tock=0.0):
while not witDoer.cues:
_ = yield self.tock

print(f'Delegagtor Prefix {hab.pre}')
print(f'\tDelegate {eserder.pre} {typ} Anchored at Seq. No. {hab.kever.sner.num}')
print(f'Delegagtor Prefix {hab.pre}')
print(f'\tDelegate {typ} event {eserder.pre} Anchored at Seq. No. {hab.kever.sner.num}')

# wait for confirmation of fully commited event
if eserder.pre in self.hby.kevers:
self.witq.query(src=hab.pre, pre=eserder.pre, sn=eserder.sn)

while eserder.sn < self.hby.kevers[eserder.pre].sn:
yield self.tock

print(f"Delegate {eserder.pre} {typ} event committed.")
else: # It should be an inception event then...
wits = [werfer.qb64 for werfer in eserder.berfers]
self.witq.query(src=hab.pre, pre=eserder.pre, sn=eserder.sn, wits=wits)

while eserder.pre not in self.hby.kevers:
yield self.tock

print(f"Delegate {eserder.pre} {typ} event committed.")
print(f"Delegate {typ} event {eserder.pre} committed.")

self.hby.db.delegables.rem(keys=(pre, sn))
self.hby.db.delegables.rem(keys=(pre, sn), val=edig)
self.remove(self.toRemove)
return True

Expand Down
2 changes: 1 addition & 1 deletion src/keri/app/habbing.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def openHab(name="test", base="", salt=None, temp=True, cf=None, **kwa):

with openHby(name=name, base=base, salt=salt, temp=temp, cf=cf) as hby:
if (hab := hby.habByName(name)) is None:
hab = hby.makeHab(name=name, icount=1, isith='1', ncount=1, nsith='1', **kwa)
hab = hby.makeHab(name=name, icount=1, isith='1', ncount=1, nsith='1', cf=cf, **kwa)

yield hby, hab

Expand Down
50 changes: 41 additions & 9 deletions src/keri/core/eventing.py
Original file line number Diff line number Diff line change
Expand Up @@ -1682,27 +1682,58 @@ def locallyOwned(self, pre: str | None = None):


def locallyDelegated(self, pre: str):
"""Returns True if pre is in .prefixes and not in .groups
False otherwise. Use when pre is a delegator for some event and
want to confirm that pre is also locallyOwned thereby making the
associated event locallyDelegated.
"""Returns True if pre w is in .prefixes which includes group AIDs in
self.groups which have a local member AID.

Which means it is either locally controlled single sig or a multi-sig
group with a locally controlled member.
False otherwise.

Use when pre is a delegator, i.e. the delpre from some delegated event and
want to confirm that pre is also locally controller as either the single
sig AID or the group multisig AID of a locally controlled member of the group.

Indicates that provided identifier prefix is controlled by a local
controller from .prefixes but is not a group with local member.
i.e pre is a locally owned (controlled) AID (identifier prefix)
controller from .prefixes is a group prefix that is controlled by a local
member of that group.

Because delpre may be None, changes the default to "" instead of
self.prefixer.pre because self.prefixer.pre is delegate not delegator
of self. Unaccepted dip events do not have self.delpre set yet.

Returns:
(bool): True if pre is local hab but not group hab
(bool): True if pre is local hab or group hab that has a local member
When pre="" empty or None then returns False

Parameters:
pre (str): qb64 identifier prefix if any.


ToDo: this code does not account for stale group members as delegators.
i.e. a stale group membed is a member AID for a group AID in .groups
for which the member AID was a signing (smids) or rotating (rmids) member
in the past but is no longer. For delegation approval there must be
a local member for the delegator group AID that is a current signing member
i,e. in .smids for the group hab.

The current logic allows an event to be escrowed for later approval
but whose delpre (delegator) is a group with a stale local member
That later approval must detect and properly handle the staleness.

Alternatively the logic could be changed to short circut that later
work by checking here for staleness. For example:
delpre.mhab.pre in delpre's hab.smids (not stale )


if pre in self.groups: # local group delegator
habord = self.db.habs.get(keys=(pre,))
return habord.mid in habord.smids # True not stale, False stale

return pre in self.prefixes # otherwise local non-group delegator

"""
pre = pre if pre is not None else ""
return self.locallyOwned(pre=pre)
return pre in self.prefixes


def locallyWitnessed(self, *, wits: list[str]=None, serder: (str)=None):
Expand Down Expand Up @@ -2388,7 +2419,8 @@ def valSigsWigsDel(self, serder, sigers, verfers, tholder,
# seal in this case can't be malicious since sourced locally.
# Doesn't get to here until fully signed and witnessed.

if self.locallyDelegated(delpre) and not self.locallyOwned(): # local delegator
# should only run for delegated inception and rotation, not interaction. Ixn does not require approval.
if serder.ilk != Ilks.ixn and self.locallyDelegated(delpre) and not self.locallyOwned(): # local delegator
# must be local if locallyDelegated or caught above as misfit
if delseqner is None or delsaider is None: # missing delegation seal
# so escrow delegable. So local delegator can approve OOB.
Expand Down
85 changes: 85 additions & 0 deletions tests/app/app_helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import json
from contextlib import contextmanager
from typing import List, Generator, Tuple, Any

from hio.base import Doer, Doist

from keri.app import habbing, delegating
from keri.app.agenting import WitnessReceiptor, Receiptor
from keri.app.configing import Configer
from keri.app.delegating import Anchorer
from keri.app.forwarding import Poster
from keri.app.habbing import openHab, HaberyDoer, Habery, Hab, openHby
from keri.app.indirecting import MailboxDirector, setupWitness
from keri.app.notifying import Notifier
from keri.core import Salter
from keri.peer.exchanging import Exchanger


@contextmanager
def openWit(name: str = 'wan', tcpPort: int = 6632, httpPort: int = 6642, salt: bytes = b'abcdefg0123456789') -> Generator[Tuple[Habery, Hab, List[Doer], str], None, None]:
"""
Context manager for a KERI witness along with the Doers needed to run it.
Expects the Doers to be run by the caller.

Returns a tuple of (Habery, Hab, witness Doers, witness controller OOBI URL)
"""
salt = Salter(raw=salt).qb64
# Witness config
witCfg = f"""{{
"dt": "2025-12-11T11:02:30.302010-07:00",
"{name}": {{
"dt": "2025-12-11T11:02:30.302010-07:00",
"curls": ["tcp://127.0.0.1:{tcpPort}/", "http://127.0.0.1:{httpPort}/"]}}}}"""
cf = Configer(name=name, temp=False, reopen=True, clear=False)
cf.put(json.loads(witCfg))
with (
openHab(salt=bytes(salt, 'utf-8'), name=name, transferable=False, temp=True, cf=cf) as (hby, hab)
):
oobi = f'http://127.0.0.1:{httpPort}/oobi/{hab.pre}/controller?name={name}&tag=witness'
hbyDoer = HaberyDoer(habery=hby)
doers: List[Doer] = [hbyDoer]
doers.extend(setupWitness(alias=name, hby=hby, tcpPort=tcpPort, httpPort=httpPort))
yield hby, hab, doers, oobi


@contextmanager
def openCtrlWited(witOobi: str, name: str = 'aceCtlrKS', salt: bytes = b'aaaaaaa0123456789') -> Generator[Tuple[Habery, List[Doer]], None, None]:
"""
Context manager for setting up a KERI controller that uses a witness as its mailbox and witness.
Sets up the Doers needed to run a controller including both single sig and multi-sig handlers.
Relies on an outer context manager or caller to perform OOBI resolution and inception of the controller AID.
Receives a witness OOBI URL to use as its configured witness.

Expects the Doers to be run by the caller.

Returns a tuple of (Habery, controller Doers)
"""
ctlrCfg = f"""{{
"dt": "2025-12-11T11:02:30.302010-07:00",
"iurls": [\"{witOobi}\"]}}"""
cf = Configer(name=name, temp=False, reopen=True, clear=False)
cf.put(json.loads(ctlrCfg))
with openHby(salt=salt, name=name, temp=True, cf=cf) as hby:
hbyDoer = habbing.HaberyDoer(habery=hby)
anchorer = Anchorer(hby=hby, proxy=None)
postman = Poster(hby=hby)
exc = Exchanger(hby=hby, handlers=[])
notifier = Notifier(hby=hby)
delegating.loadHandlers(hby=hby, exc=exc, notifier=notifier)
mbx = MailboxDirector(hby=hby, exc=exc, topics=['/receipt', '/replay', '/reply', '/delegate', '/multisig'])
witReceiptor = WitnessReceiptor(hby=hby)
receiptor = Receiptor(hby=hby)
doers = [hbyDoer, anchorer, postman, mbx, witReceiptor, receiptor]
yield hby, doers

@contextmanager
def openCtrlWitIcpd(
doist: Doist, witOobi: str, witDoers: List[Doer],
name: str = 'aceCtlrKS',
salt: bytes = b'aaaaaaa0123456789',
alias='aceCtlrAIC'):
"""
Uses the Doist to perform both OOBI resolution of the witness and inception of the controller AID.
"""
pass
Loading
Loading