diff --git a/setup.py b/setup.py index ea3f57799..ee4d6997a 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,12 @@ $ twine upload dist/keri-0.0.1.tar.gz Create release git: +$ git tag # lists all tags +$ git tag -a v0.6.11 -m "new feature" +$ git show v0.6.11 +$ git push --tags # pushes tags to default remote +$ git push wot --tags # pushes tags to wot remote + $ git tag -a v0.4.2 -m "bump version" $ git push --tags $ git checkout -b release_0.4.2 @@ -70,31 +76,31 @@ ], python_requires='>=3.12.2', install_requires=[ - 'lmdb>=1.3.0', - 'pysodium>=0.7.12', - 'blake3>=0.3.1', - 'msgpack>=1.0.4', + 'lmdb>=1.4.1', + 'pysodium>=0.7.17', + 'blake3>=0.4.1', + 'msgpack>=1.0.8', 'cbor2>=5.6.2', - 'multidict>=6.0.2', + 'multidict>=6.0.5', 'ordered-set>=4.1.0', - 'hio>=0.6.11', + 'hio>=0.6.12', 'multicommand>=1.0.0', - 'jsonschema>=4.17.0', - 'falcon>=3.1.0', - 'hjson>=3.0.2', - 'PyYaml>=6.0', - 'apispec>=6.0.0', - 'mnemonic>=0.20', - 'PrettyTable>=3.5.0', - 'http_sfv>=0.9.8', + 'jsonschema>=4.21.1', + 'falcon>=3.1.3', + 'hjson>=3.1.0', + 'PyYaml>=6.0.1', + 'apispec>=6.6.0', + 'mnemonic>=0.21', + 'PrettyTable>=3.10.0', + 'http_sfv>=0.9.9', 'cryptography>=42.0.5', 'semver>=3.0.2' ], extras_require={ }, tests_require=[ - 'coverage>=6.5.0', - 'pytest>=7.2.0', + 'coverage>=7.4.4', + 'pytest>=8.1.1', 'pytest-shell>=0.3.2' ], setup_requires=[ diff --git a/src/keri/app/cli/commands/incept.py b/src/keri/app/cli/commands/incept.py index 29ff39cf6..084d6315c 100644 --- a/src/keri/app/cli/commands/incept.py +++ b/src/keri/app/cli/commands/incept.py @@ -139,7 +139,7 @@ def __init__(self, name, base, alias, bran, endpoint, proxy=None, cnfg=None, **k self.proxy = proxy hby = existing.setupHby(name=name, base=base, bran=bran, cf=cf) self.hbyDoer = habbing.HaberyDoer(habery=hby) # setup doer - self.swain = delegating.Sealer(hby=hby) + self.swain = delegating.Anchorer(hby=hby) self.postman = forwarding.Poster(hby=hby) self.mbx = indirecting.MailboxDirector(hby=hby, topics=['/receipt', "/replay", "/reply"]) doers = [self.hbyDoer, self.postman, self.mbx, self.swain, doing.doify(self.inceptDo)] diff --git a/src/keri/app/cli/commands/multisig/join.py b/src/keri/app/cli/commands/multisig/join.py index e52dae7a1..4f3285a7c 100644 --- a/src/keri/app/cli/commands/multisig/join.py +++ b/src/keri/app/cli/commands/multisig/join.py @@ -167,8 +167,8 @@ def incept(self, attrs): inits["isith"] = ked["kt"] inits["nsith"] = ked["nt"] - inits["estOnly"] = eventing.TraitCodex.EstOnly in ked["c"] - inits["DnD"] = eventing.TraitCodex.DoNotDelegate in ked["c"] + inits["estOnly"] = eventing.TraitDex.EstOnly in ked["c"] + inits["DnD"] = eventing.TraitDex.DoNotDelegate in ked["c"] inits["toad"] = ked["bt"] inits["wits"] = ked["b"] @@ -273,8 +273,8 @@ def showEvent(self, hab, mids, ked): if not thold.weighted: tab.add_row(["Signature Threshold", thold.num]) - tab.add_row(["Establishment Only", eventing.TraitCodex.EstOnly in ked["c"]]) - tab.add_row(["Do Not Delegate", eventing.TraitCodex.DoNotDelegate in ked["c"]]) + tab.add_row(["Establishment Only", eventing.TraitDex.EstOnly in ked["c"]]) + tab.add_row(["Do Not Delegate", eventing.TraitDex.DoNotDelegate in ked["c"]]) tab.add_row(["Witness Threshold", ked["bt"]]) tab.add_row(["Witnesses", "\n".join(ked["b"])]) diff --git a/src/keri/app/cli/commands/rotate.py b/src/keri/app/cli/commands/rotate.py index 73d2cda2b..56092c617 100644 --- a/src/keri/app/cli/commands/rotate.py +++ b/src/keri/app/cli/commands/rotate.py @@ -147,7 +147,7 @@ def __init__(self, name, base, bran, alias, endpoint=False, isith=None, nsith=No self.hby = existing.setupHby(name=name, base=base, bran=bran) self.hbyDoer = habbing.HaberyDoer(habery=self.hby) # setup doer - self.swain = delegating.Sealer(hby=self.hby) + self.swain = delegating.Anchorer(hby=self.hby) self.postman = forwarding.Poster(hby=self.hby) self.mbx = indirecting.MailboxDirector(hby=self.hby, topics=['/receipt', "/replay", "/reply"]) doers = [self.hbyDoer, self.mbx, self.swain, self.postman, doing.doify(self.rotateDo)] diff --git a/src/keri/app/delegating.py b/src/keri/app/delegating.py index 475b027f3..0823af1b1 100644 --- a/src/keri/app/delegating.py +++ b/src/keri/app/delegating.py @@ -19,8 +19,8 @@ logger = help.ogler.getLogger() -class Sealer(doing.DoDoer): - """ +class Anchorer(doing.DoDoer): + """Anchorer subclass of DoDoer Sends messages to Delegator of an identifier and wait for the anchoring event to be processed to ensure the inception or rotation event has been approved by the delegator. @@ -46,7 +46,7 @@ def __init__(self, hby, proxy=None, **kwa): self.witDoer = agenting.Receiptor(hby=self.hby) self.proxy = proxy - super(Sealer, self).__init__(doers=[self.witq, self.witDoer, self.postman, doing.doify(self.escrowDo)], + super(Anchorer, self).__init__(doers=[self.witq, self.witDoer, self.postman, doing.doify(self.escrowDo)], **kwa) def delegation(self, pre, sn=None, proxy=None): diff --git a/src/keri/app/grouping.py b/src/keri/app/grouping.py index 0495ce9f3..643aa7222 100644 --- a/src/keri/app/grouping.py +++ b/src/keri/app/grouping.py @@ -25,7 +25,7 @@ class Counselor(doing.DoDoer): def __init__(self, hby, swain=None, proxy=None, **kwa): self.hby = hby - self.swain = swain if swain is not None else delegating.Sealer(hby=self.hby) + self.swain = swain if swain is not None else delegating.Anchorer(hby=self.hby) self.proxy = proxy self.witDoer = agenting.Receiptor(hby=self.hby) self.witq = agenting.WitnessInquisitor(hby=hby) diff --git a/src/keri/app/habbing.py b/src/keri/app/habbing.py index eba307ea5..eb81326c8 100644 --- a/src/keri/app/habbing.py +++ b/src/keri/app/habbing.py @@ -381,7 +381,7 @@ def makeHab(self, name, ns=None, cf=None, **kwa): toad (Union[int,str]): int or str hex of witness threshold wits (list): of qb64 prefixes of witnesses delpre (str): qb64 of delegator identifier prefix - estOnly (str): eventing.TraitCodex.EstOnly means only establishment + estOnly (str): eventing.TraitDex.EstOnly means only establishment events allowed in KEL for this Hab data (list | None): seal dicts """ @@ -427,9 +427,9 @@ def makeGroupHab(self, group, mhab, smids, rmids=None, ns=None, **kwa): toad (Union[int,str]): int or str hex of witness threshold wits (list): of qb64 prefixes of witnesses delpre (str): qb64 of delegator identifier prefix - estOnly (str): eventing.TraitCodex.EstOnly means only establishment + estOnly (str): eventing.TraitDex.EstOnly means only establishment events allowed in KEL for this Hab - DnD (bool): eventing.TraitCodex.DnD means do allow delegated identifiers from this identifier + DnD (bool): eventing.TraitDex.DnD means do allow delegated identifiers from this identifier ToDo: NRR add midxs tuples for each group member or all in group multisig. @@ -1006,10 +1006,10 @@ def make(self, DnD, code, data, delpre, estOnly, isith, verfers, nsith, digers, specified else compute default based on number of wits (backers) wits (list | None): qb64 prefixes of witnesses if any delpre (str | None): qb64 of delegator identifier prefix if any - estOnly (bool | None): True means add trait eventing.TraitCodex.EstOnly + estOnly (bool | None): True means add trait eventing.TraitDex.EstOnly which means only establishment events allowed in KEL for this Hab False (default) means allows non-est events and no trait is added. - DnD (bool): True means add trait of eventing.TraitCodex.DnD which + DnD (bool): True means add trait of eventing.TraitDex.DnD which means do not allow delegated identifiers from this identifier False (default) means do allow and no trait is added. @@ -1026,9 +1026,9 @@ def make(self, DnD, code, data, delpre, estOnly, isith, verfers, nsith, digers, nst = coring.Tholder(sith=nsith).sith # next signing threshold cnfg = [] if estOnly: - cnfg.append(eventing.TraitCodex.EstOnly) + cnfg.append(eventing.TraitDex.EstOnly) if DnD: - cnfg.append(eventing.TraitCodex.DoNotDelegate) + cnfg.append(eventing.TraitDex.DoNotDelegate) self.delpre = delpre keys = [verfer.qb64 for verfer in verfers] if self.delpre: @@ -2207,10 +2207,10 @@ def make(self, *, secrecies=None, iridx=0, code=coring.MtrDex.Blake3_256, dcode= specified else compute default based on number of wits (backers) wits (list | None): qb64 prefixes of witnesses if any delpre (str | None): qb64 of delegator identifier prefix if any - estOnly (bool | None): True means add trait eventing.TraitCodex.EstOnly + estOnly (bool | None): True means add trait eventing.TraitDex.EstOnly which means only establishment events allowed in KEL for this Hab False (default) means allows non-est events and no trait is added. - DnD (bool): True means add trait of eventing.TraitCodex.DnD which + DnD (bool): True means add trait of eventing.TraitDex.DnD which means do not allow delegated identifiers from this identifier False (default) means do allow and no trait is added. @@ -2671,10 +2671,10 @@ def make(self, *, code=coring.MtrDex.Blake3_256, transferable=True, isith=None, specified else compute default based on number of wits (backers) wits (list | None): qb64 prefixes of witnesses if any delpre (str | None): qb64 of delegator identifier prefix if any - estOnly (bool | None): True means add trait eventing.TraitCodex.EstOnly + estOnly (bool | None): True means add trait eventing.TraitDex.EstOnly which means only establishment events allowed in KEL for this Hab False (default) means allows non-est events and no trait is added. - DnD (bool): True means add trait of eventing.TraitCodex.DnD which + DnD (bool): True means add trait of eventing.TraitDex.DnD which means do not allow delegated identifiers from this identifier False (default) means do allow and no trait is added. merfers (list[Verfer] | None): group member Verfer instances of diff --git a/src/keri/core/coring.py b/src/keri/core/coring.py index f02ad5c60..045ee8675 100644 --- a/src/keri/core/coring.py +++ b/src/keri/core/coring.py @@ -6,10 +6,9 @@ import re import json from typing import Union +from collections import namedtuple, deque from collections.abc import Sequence, Mapping - from dataclasses import dataclass, astuple -from collections import namedtuple, deque from base64 import urlsafe_b64encode as encodeB64 from base64 import urlsafe_b64decode as decodeB64 from fractions import Fraction @@ -40,7 +39,8 @@ from ..kering import (Versionage, Version, Vrsn_1_0, Vrsn_2_0, VERRAWSIZE, VERFMT, MAXVERFULLSPAN, versify, deversify, Rever, smell) -from ..kering import Serials, Serialage, Protocols, Protocolage, Ilkage, Ilks +from ..kering import (Serials, Serialage, Protocols, Protocolage, Ilkage, Ilks, + TraitDex, ) from ..help import helping from ..help.helping import sceil, nonStringIterable, nonStringSequence @@ -182,22 +182,25 @@ def loads(raw, size=None, kind=Serials.json): return ked -# deprecated don't use anymore need to fix demo tests that use -# use with context instead -def generateSigners(salt=None, count=8, transferable=True): - """ - Returns list of Signers for Ed25519 + +def generateSigners(raw=None, count=8, transferable=True): + """Returns list of Signers for Ed25519 + + Deprecated, use Salter.signers instead. + + Use this when simply need valid AIDs but not when need valid controller + contexts. In the latter case use openHby or openHab which create databases. Parameters: - salt is bytes 16 byte long root cryptomatter from which seeds for Signers - in list are derived + raw (bytes): 16 byte long salt cryptomatter from which seeds + for Signers in list are derived random salt created if not provided count is number of signers in list transferable is boolean true means signer.verfer code is transferable non-transferable otherwise """ - if not salt: - salt = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES) + if not raw: + raw = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES) signers = [] for i in range(count): @@ -205,7 +208,7 @@ def generateSigners(salt=None, count=8, transferable=True): # algorithm default is argon2id seed = pysodium.crypto_pwhash(outlen=32, passwd=path, - salt=salt, + salt=raw, opslimit=2, # pysodium.crypto_pwhash_OPSLIMIT_INTERACTIVE, memlimit=67108864, # pysodium.crypto_pwhash_MEMLIMIT_INTERACTIVE, alg=pysodium.crypto_pwhash_ALG_ARGON2ID13) @@ -215,36 +218,6 @@ def generateSigners(salt=None, count=8, transferable=True): return signers -def generatePrivates(salt=None, count=8): - """ - Returns list of fully qualified Base64 secret Ed25519 seeds i.e private keys - - Parameters: - salt is bytes 16 byte long root cryptomatter from which seeds for Signers - in list are derived - random salt created if not provided - count is number of signers in list - """ - signers = generateSigners(salt=salt, count=count) - - return [signer.qb64 for signer in signers] # fetch sigkey as private key - - -def generatePublics(salt=None, count=8, transferable=True): - """ - Returns list of fully qualified Base64 secret seeds for Ed25519 private keys - - Parameters: - salt is bytes 16 byte long root cryptomatter from which seeds for Signers - in list are derived - random salt created if not provided - count is number of signers in list - """ - signers = generateSigners(salt=salt, count=count, transferable=transferable) - - return [signer.verfer.qb64 for signer in signers] # fetch verkey as public key - - # secret derivation security tier Tierage = namedtuple("Tierage", 'low med high') @@ -1446,7 +1419,6 @@ def _bexfil(self, qb2): qb2 = qb2[:bfs] # extract qb2 fully qualified primitive code plus material - # check for nonzero trailing full code mid pad bits ps = cs % 4 # full code (both) net pad size for 24 bit alignment pbs = 2 * ps # mid pad bits = 2 per net pad @@ -2055,6 +2027,215 @@ def tag(self): return tag +class Ilker(Tagger): + """ + Ilker is subclass of Tagger, cryptographic material, for formatted + message types (ilks) in Base64. Leverages Tagger support compact special + fixed size primitives with non-empty soft part and empty raw part. + + Ilker provides a more compact representation than would be obtained by + converting the raw ASCII representation to Base64. + + Attributes: + + Inherited Properties: (See Tagger) + code (str): hard part of derivation code to indicate cypher suite + hard (str): hard part of derivation code. alias for code + soft (str): soft part of derivation code fs any. + Empty when ss = 0. + both (str): hard + soft parts of full text code + size (int | None): Number of quadlets/triplets of chars/bytes including + lead bytes of variable sized material (fs = None). + Converted value of the soft part (of len ss) of full + derivation code. + Otherwise None when not variably sized (fs != None) + fullSize (int): full size of primitive + raw (bytes): crypto material only. Not derivation code or lead bytes. + qb64 (str): Base64 fully qualified with derivation code + crypto mat + qb64b (bytes): Base64 fully qualified with derivation code + crypto mat + qb2 (bytes): binary with derivation code + crypto material + transferable (bool): True means transferable derivation code False otherwise + digestive (bool): True means digest derivation code False otherwise + prefixive (bool): True means identifier prefix derivation code False otherwise + special (bool): True when soft is special raw is empty and fixed size + composable (bool): True when .qb64b and .qb2 are 24 bit aligned and round trip + tag (str): B64 primitive without prepad (strips prepad from soft) + + + Properties: + ilk (str): message type from Ilks of Ilkage + + Inherited Hidden: (See Tagger) + _code (str): value for .code property + _soft (str): soft value of full code + _raw (bytes): value for .raw property + _rawSize(): + _leadSize(): + _special(): + _infil(): creates qb64b from .raw and .code (fully qualified Base64) + _binfil(): creates qb2 from .raw and .code (fully qualified Base2) + _exfil(): extracts .code and .raw from qb64b (fully qualified Base64) + _bexfil(): extracts .code and .raw from qb2 (fully qualified Base2) + + Hidden: + + + Methods: + + """ + + + def __init__(self, qb64b=None, qb64=None, qb2=None, tag='', ilk='', **kwa): + """ + Inherited Parameters: (see Tagger) + raw (bytes | bytearray | None): unqualified crypto material usable + for crypto operations. + code (str): stable (hard) part of derivation code + soft (str | bytes): soft part for special codes + rize (int | None): raw size in bytes when variable sized material not + including lead bytes if any + Otherwise None + qb64b (bytes | None): fully qualified crypto material Base64 + qb64 (str | bytes | None): fully qualified crypto material Base64 + qb2 (bytes | None): fully qualified crypto material Base2 + strip (bool): True means strip (delete) matter from input stream + bytearray after parsing qb64b or qb2. False means do not strip + tag (str | bytes): Base64 plain. Prepad is added as needed. + + Parameters: + ilk (str): message type from Ilks of Ilkage + + """ + if not (qb64b or qb64 or qb2): + if ilk: + tag = ilk + + + super(Ilker, self).__init__(qb64b=qb64b, qb64=qb64, qb2=qb2, tag=tag, **kwa) + + if self.code not in (MtrDex.Tag3, ): + raise InvalidCodeError(f"Invalid code={self.code} for Ilker " + f"{self.ilk=}.") + if self.ilk not in Ilks: + raise InvalidSoftError(f"Ivalid ilk={self.ilk} for Ilker.") + + + + @property + def ilk(self): + """Returns: + tag (str): B64 primitive without prepad (strips prepad from soft) + + Alias for self.tag + + """ + return self.tag + + +class Traitor(Tagger): + """ + Traitor is subclass of Tagger, cryptographic material, for formatted + configuration traits for key events in Base64. Leverages Tagger support of + compact special fixed size primitives with non-empty soft part and empty raw part. + + Traitor provides a more compact representation than would be obtained by + converting the raw ASCII representation to Base64. + + Attributes: + + Inherited Properties: (See Tagger) + code (str): hard part of derivation code to indicate cypher suite + hard (str): hard part of derivation code. alias for code + soft (str): soft part of derivation code fs any. + Empty when ss = 0. + both (str): hard + soft parts of full text code + size (int | None): Number of quadlets/triplets of chars/bytes including + lead bytes of variable sized material (fs = None). + Converted value of the soft part (of len ss) of full + derivation code. + Otherwise None when not variably sized (fs != None) + fullSize (int): full size of primitive + raw (bytes): crypto material only. Not derivation code or lead bytes. + qb64 (str): Base64 fully qualified with derivation code + crypto mat + qb64b (bytes): Base64 fully qualified with derivation code + crypto mat + qb2 (bytes): binary with derivation code + crypto material + transferable (bool): True means transferable derivation code False otherwise + digestive (bool): True means digest derivation code False otherwise + prefixive (bool): True means identifier prefix derivation code False otherwise + special (bool): True when soft is special raw is empty and fixed size + composable (bool): True when .qb64b and .qb2 are 24 bit aligned and round trip + tag (str): B64 primitive without prepad (strips prepad from soft) + + + Properties: + trait (str): configuration trait B64 from TraitDex + + Inherited Hidden: (See Tagger) + _code (str): value for .code property + _soft (str): soft value of full code + _raw (bytes): value for .raw property + _rawSize(): + _leadSize(): + _special(): + _infil(): creates qb64b from .raw and .code (fully qualified Base64) + _binfil(): creates qb2 from .raw and .code (fully qualified Base2) + _exfil(): extracts .code and .raw from qb64b (fully qualified Base64) + _bexfil(): extracts .code and .raw from qb2 (fully qualified Base2) + + Hidden: + + + Methods: + + """ + + + def __init__(self, qb64b=None, qb64=None, qb2=None, tag='', trait='', **kwa): + """ + Inherited Parameters: (see Tagger) + raw (bytes | bytearray | None): unqualified crypto material usable + for crypto operations. + code (str): stable (hard) part of derivation code + soft (str | bytes): soft part for special codes + rize (int | None): raw size in bytes when variable sized material not + including lead bytes if any + Otherwise None + qb64b (bytes | None): fully qualified crypto material Base64 + qb64 (str | bytes | None): fully qualified crypto material Base64 + qb2 (bytes | None): fully qualified crypto material Base2 + strip (bool): True means strip (delete) matter from input stream + bytearray after parsing qb64b or qb2. False means do not strip + tag (str | bytes): Base64 plain. Prepad is added as needed. + + Parameters: + trait (str): configuration trait B64 from TraitDex + + """ + if not (qb64b or qb64 or qb2): + if trait: + tag = trait + + + super(Traitor, self).__init__(qb64b=qb64b, qb64=qb64, qb2=qb2, tag=tag, **kwa) + + + if self.trait not in TraitDex: + raise InvalidSoftError(f"Invalid trait={self.trait} for Traitor.") + + + + @property + def trait(self): + """Returns: + trait (str): B64 primitive without prepad (strips prepad from soft) + + Alias for self.tag + + """ + return self.tag + + + # Versage namedtuple # proto (str): protocol element of Protocols @@ -2147,8 +2328,6 @@ def __init__(self, qb64b=None, qb64=None, qb2=None, versage=None, gvrsn (Versionage | None): instance genus version. namedtuple (major, minor) of ints - Notes: - prepad = 'A' """ if not (qb64b or qb64 or qb2): if versage: @@ -2162,8 +2341,8 @@ def __init__(self, qb64b=None, qb64=None, qb2=None, versage=None, super(Verser, self).__init__(qb64b=qb64b, qb64=qb64, qb2=qb2, tag=tag, **kwa) if self.code not in (MtrDex.Tag7, MtrDex.Tag10, ): - raise InvalidCodeError(f"Invalid code={self.code} for Verser " - f"{self.tag=}.") + raise InvalidCodeError(f"Invalid code={self.code} for " + f"Verser={self.tag}.") @property @@ -4547,7 +4726,7 @@ def __iter__(self): # hs is the hard size int number of chars in hard (stable) part of code # ss is the soft size int number of chars in soft (unstable) part of code # os is the other size int number of chars in other index part of soft -# ms = ss - os main index size computed +# ms = ss - os main index size computed # fs is the full size int number of chars in code plus appended material if any # ls is the lead size int number of bytes to pre-pad pre-converted raw binary Xizage = namedtuple("Xizage", "hs ss os fs ls") diff --git a/src/keri/core/eventing.py b/src/keri/core/eventing.py index 1d3e860f8..ca93cab29 100644 --- a/src/keri/core/eventing.py +++ b/src/keri/core/eventing.py @@ -33,7 +33,8 @@ UnverifiedReceiptError, UnverifiedTransferableReceiptError, QueryNotFoundError, MisfitEventSourceError, MissingDelegableApprovalError) -from ..kering import Version, Versionage +from ..kering import Version, Versionage, TraitCodex, TraitDex +from ..kering import Coldage, Colds, ColdDex from ..help import helping @@ -43,25 +44,6 @@ MaxIntThold = 2 ** 32 - 1 -@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 - NoRegistrarBackers: str = 'NB' # This should be NRB in next versionDo not allow any registrar backers - RegistrarBackers: 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') @@ -121,53 +103,6 @@ def __iter__(self): StateEvent = namedtuple("StateEvent", 's t d') -@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) @@ -818,32 +753,6 @@ def incept(keys, 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 def delcept(keys, delpre, **kwa): """ @@ -1852,8 +1761,8 @@ def reload(self, state): 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.doNotDelegate = True if TraitDex.DoNotDelegate in state.c else False + self.estOnly = True if TraitDex.EstOnly in state.c else False self.lastEst = LastEstLoc(s=int(state.ee.s, 16), d=state.ee.d) self.delpre = state.di if state.di else None diff --git a/src/keri/core/serdering.py b/src/keri/core/serdering.py index 89bf53807..cb237303e 100644 --- a/src/keri/core/serdering.py +++ b/src/keri/core/serdering.py @@ -30,7 +30,8 @@ from ..kering import Protocols, Serials, Rever, versify, deversify, Ilks from ..core import coring from .coring import MtrDex, DigDex, PreDex, Saids, Digestage -from .coring import Matter, Saider, Verfer, Diger, Number, Tholder, Verser +from .coring import (Matter, Saider, Verfer, Diger, Number, Tholder, Tagger, + Ilker, Traitor, Verser, ) from ..core import counting from ..core.counting import GenDex, AllTags, Counter @@ -1242,11 +1243,11 @@ def _dumps(self, sad): # should dispatch or use match instead of big if else match l: # label - case "v": # protocol+version + case "v": # protocol+version do not use version string itself val = Verser(proto=self.proto, vrsn=self.vrsn).qb64b - case "t": # message type - val = (MtrDex.Tag3 + ilk).encode("utf-8") # add code + case "t": # message type (ilk), already got ilk + val = Ilker(ilk=v).qb64b # assumes same case "d" | "i" | "p" | "di": # said or aid val = v.encode("utf-8") # already primitive qb64 make qb6b @@ -1270,30 +1271,39 @@ def _dumps(self, sad): case "c": # list of config traits strings frame = bytearray() for e in v: # list - pass - #frame.extend(e.encode("utf-8")) + frame.extend(Traitor(trait=e).qb64n) val = bytearray(Counter(tag=AllTags.GenericListGroup, - count=len(frame) % 4, - version=self.gvrsn).qb64b) + count=len(frame) % 4).qb64b) val.extend(frame) case "a": # list of seals or field map of attributes frame = bytearray() - if isinstance(v, Mapping): - for l, e in v.items(): - pass - val = bytearray(Counter(tag=AllTags.GenericMapGroup, - count=len(frame) % 4, - version=self.gvrsn).qb64b) - else: - for e in v: # list - pass - #frame.extend(e.encode("utf-8")) + for e in v: # list of seal dicts + pass + #if tuple(v) == eventing.SealEvent._fields: + #eseal = eventing.SealEvent(**v) # convert to namedtuple + #SealSourceCouples: str = '-Q' # Seal Source Couple(s), snu+dig of source sealing or sealed event. + #SealSourceTriples: str = '-R' # Seal Source Triple(s), pre+snu+dig of source sealing or sealed event. + #DigestSealSingles: str = '-V' # Digest Seal Single(s), dig of sealed data. + #MerkleRootSealSingles: str = '-W' # Merkle Tree Root Digest Seal Single(s), dig of sealed data. + #BackerRegistrarSealCouples: str = '-X' # Backer Registrar Seal Couple(s), brid+dig of sealed data. + + # SealMark == tuple of seal dict field names tuple(dict) + #d = dict(a=1, b=2) + #tuple(d) + #('a', 'b') + + #frame.extend(Anchor(seal=e).qb64b) + # else: generic seal no count type (v, Mapping): + #for l, e in v.items(): + #pass + #val = bytearray(Counter(tag=AllTags.GenericMapGroup, + # count=len(frame) % 4).qb64b) + #val.extend(mapframe) - val = bytearray(Counter(tag=AllTags.GenericListGroup, - count=len(frame) % 4, - version=self.gvrsn).qb64b) + val = bytearray(Counter(tag=AllTags.GenericListGroup, + count=len(frame) % 4).qb64b) val.extend(frame) @@ -1319,7 +1329,10 @@ def _dumps(self, sad): # prepend count code for message if fixed: - pass + + val = bytearray(Counter(tag=AllTags.FixedMessageBodyGroup, + count=len(raw) % 4).qb64b) + val.extend(raw) else: pass diff --git a/src/keri/kering.py b/src/keri/kering.py index 0ab43c76a..aee3481a7 100644 --- a/src/keri/kering.py +++ b/src/keri/kering.py @@ -57,12 +57,6 @@ SMELLSIZE = MAXVSOFFSET + MAXVERFULLSPAN # min buffer size to inhale -# version field in CESR native serialization -VFFULLSPAN = 12 # number of characters in full version string -VFREX = b'0N(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})' - -Revfer = re.compile(VFREX) # compile is faster - """ Smellage (results of smelling a version string such as in a Serder) @@ -209,69 +203,6 @@ def smell(raw): return rematch(match) -def snatch(match, size=0): - """ Returns: - smellage (Smellage): named tuple extracted from version string regex match - (protocol, version, kind, size) - - Parameters: - match (re.Match): instance of Match class - size (int): provided size to substitute when missing - - Notes: - regular expressions work with memoryview objects not just bytes or - bytearrays - """ - full = match.group() # full matched version string - if len(full) == VFFULLSPAN: - proto, major, minor, gmajor, gminor = match.group("proto0", - "major0", - "minor0", - "gmajor0", - "gminor0") - proto = proto.decode("utf-8") - if proto not in Protocols: - raise ProtocolError(f"Invalid protocol type = {proto}.") - vrsn = Versionage(major=b64ToInt(major), minor=b64ToInt(minor)) - if vrsn.major < 2: # version2 vs but major < 2 - raise VersionError(f"Incompatible {vrsn=} with version string.") - - gvrsn = Versionage(major=b64ToInt(gmajor), minor=b64ToInt(gminor)) - if gvrsn.major < 2: # version2 vs but major < 2 - raise VersionError(f"Incompatible {gvrsn=} with CESR native version" - f"field.") - kind = Serials.cesr - size = size - else: - raise VersionError(f"Bad snatch.") - - return Smellage(proto=proto, vrsn=vrsn, kind=kind, size=size, gvrsn=gvrsn) - - -def snuff(raw, size=0): - """Extract and return instance of Smellage from version string inside - raw serialization. - - Returns: - smellage (Smellage): named Tuple of (protocol, version, kind, size) - - Parameters: - raw (bytearray) of serialized incoming message stream. Assumes start - of stream is JSON, CBOR, or MGPK field map with first field - is labeled 'v' and value is version string. - size (int): provided size to substitute when missing - - """ - if len(raw) < SMELLSIZE: - raise ShortageError(f"Need more raw bytes to smell full version string.") - - match = Rever.search(raw) # Rever regex takes bytes/bytearray not str - if not match or match.start() > MAXVSOFFSET: - raise VersionError(f"Invalid version string from smelled raw = " - f"{raw[: SMELLSIZE]}.") - - return snatch(match, size=size) - @dataclass(frozen=True) class ColdCodex: @@ -281,19 +212,20 @@ class ColdCodex: Undefined are left out so that inclusion(exclusion) via 'in' operator works. First three bits: - 0o0 = 000 free + 0o0 = 000 annotated B64 (exhaustive) 0o1 = 001 cntcode B64 0o2 = 010 opcode B64 0o3 = 011 json - 0o4 = 100 mgpk + 0o4 = 100 mgpk1 0o5 = 101 cbor - 0o6 = 110 mgpk + 0o6 = 110 mgpk2 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,) + 'ann' if trited in (ColdDex.AnB64) otherwise raise ColdStartError @@ -302,7 +234,7 @@ class ColdCodex: x[0] >> 5 == 0o1 True """ - Anno: int = 0o0 # Annotated CESR + AnB64: int = 0o0 # Annotated CESR CtB64: int = 0o1 # CountCode Base64 OpB64: int = 0o2 # OpCode Base64 JSON: int = 0o3 # JSON Map Event Start @@ -413,6 +345,28 @@ def sniff(ims): watcher='watcher', judge='judge', juror='juror', peer='peer', mailbox="mailbox", agent="agent") +@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. Inception only. + DoNotDelegate: str = 'DND' # Dot not allow delegated identifiers. Inception only. + RegistrarBackers: str = 'RB' # Registrar backer provided in Registrar seal in this event + NoBackers: str = 'NB' # Do not allow any (registrar backers). + # Inception and Rotation in v2. This should be NRB in next version. + NoRegistrarBackers: str = 'NRB' # Do not allow any registrar backers. Inception and Rotation. + DelegateIsDelegator: str = 'DID' # Treat delegate AIDs same as their delegator. Inception only + + def __iter__(self): + return iter(astuple(self)) + + +TraitDex = TraitCodex() # Make instance + # Exception Subclasses class KeriError(Exception): diff --git a/src/keri/vdr/credentialing.py b/src/keri/vdr/credentialing.py index f26a01b9d..8a85c8e54 100644 --- a/src/keri/vdr/credentialing.py +++ b/src/keri/vdr/credentialing.py @@ -279,7 +279,7 @@ def make(self, *, nonce=None, noBackers=True, baks=None, toad=None, estOnly=Fals if vcp is None: baks = baks if baks is not None else [] - self.cnfg = [TraitDex.NoRegistrarBackers] if noBackers else [] + self.cnfg = [TraitDex.NoBackers] if noBackers else [] if estOnly: self.cnfg.append(TraitDex.EstOnly) diff --git a/src/keri/vdr/eventing.py b/src/keri/vdr/eventing.py index 47a1b54be..baa6eed41 100644 --- a/src/keri/vdr/eventing.py +++ b/src/keri/vdr/eventing.py @@ -69,7 +69,7 @@ def incept( cnfg = cnfg if cnfg is not None else [] baks = baks if baks is not None else [] - if TraitDex.NoRegistrarBackers in cnfg and len(baks) > 0: + if TraitDex.NoBackers in cnfg and len(baks) > 0: raise ValueError("{} backers specified for NB vcp, 0 allowed".format(len(baks))) if len(oset(baks)) != len(baks): @@ -763,7 +763,7 @@ def reload(self, rsr): self.toad = int(ked["bt"], 16) self.baks = ked["b"] - self.noBackers = True if TraitDex.NoRegistrarBackers in ked["c"] else False + self.noBackers = True if TraitDex.NoBackers in ked["c"] else False self.estOnly = True if TraitDex.EstOnly in ked["c"] else False if (raw := self.reger.getTvt(key=dgKey(pre=self.prefixer.qb64, @@ -784,7 +784,7 @@ def state(self): #state(self, kind=Serials.json) cnfg = [] if self.noBackers: - cnfg.append(TraitDex.NoRegistrarBackers) + cnfg.append(TraitDex.NoBackers) dgkey = dbing.dgKey(self.regk, self.serder.said) couple = self.reger.getAnc(dgkey) @@ -871,7 +871,7 @@ def config(self, serder, noBackers=None, estOnly=None): else False) # ensure default estOnly is boolean cnfg = serder.ked["c"] # process cnfg for traits - if TraitDex.NoRegistrarBackers in cnfg: + if TraitDex.NoBackers in cnfg: self.noBackers = True if TraitDex.EstOnly in cnfg: self.estOnly = True @@ -1758,7 +1758,7 @@ def processReplyRegistryTxnState(self, *, serder, saider, route, cigars=None, ts # Load backers from either tsn or Kever of issuer cnfg = rsr.c - if TraitDex.NoRegistrarBackers in cnfg: + if TraitDex.NoBackers in cnfg: kevers = self.kevers[pre] baks = kevers.wits else: diff --git a/tests/app/test_delegating.py b/tests/app/test_delegating.py index d66bb6dfd..e94680e2e 100644 --- a/tests/app/test_delegating.py +++ b/tests/app/test_delegating.py @@ -12,14 +12,14 @@ from keri.db import dbing -def test_sealer(seeder): +def test_anchorer(seeder): with habbing.openHby(name="wes", salt=coring.Salter(raw=b'wess-the-witness').qb64) as wesHby, \ habbing.openHby(name="pal", salt=coring.Salter(raw=b'0123456789abcdef').qb64) as palHby, \ habbing.openHby(name="del", salt=coring.Salter(raw=b'0123456789ghijkl').qb64) as delHby: wesDoers = indirecting.setupWitness(alias="wes", hby=wesHby, tcpPort=5634, httpPort=5644) witDoer = agenting.Receiptor(hby=palHby) - bts = delegating.Sealer(hby=delHby) + bts = delegating.Anchorer(hby=delHby) wesHab = wesHby.habByName(name="wes") seeder.seedWitEnds(palHby.db, witHabs=[wesHab], protocols=[kering.Schemes.http]) @@ -33,7 +33,7 @@ def test_sealer(seeder): bts=bts ) - doers = wesDoers + [witDoer, bts, doing.doify(sealer_test_do, **opts)] + doers = wesDoers + [witDoer, bts, doing.doify(anchorer_test_do, **opts)] limit = 1.0 tock = 0.03125 @@ -60,7 +60,7 @@ def test_sealer(seeder): assert bytes(delHby.db.getAes(dgkey)) == couple -def sealer_test_do(tymth=None, tock=0.0, **opts): +def anchorer_test_do(tymth=None, tock=0.0, **opts): yield tock # enter context wesHab = opts["wesHab"] diff --git a/tests/core/test_coring.py b/tests/core/test_coring.py index 32963378f..ae13bca9c 100644 --- a/tests/core/test_coring.py +++ b/tests/core/test_coring.py @@ -28,15 +28,16 @@ ShortageError, InvalidCodeSizeError, InvalidVarIndexError, InvalidValueError, DeserializeError, ValidationError, InvalidVarRawSizeError, ConversionError, - SoftMaterialError, InvalidSoftError) + SoftMaterialError, InvalidSoftError, InvalidCodeError) from keri.kering import Version, Versionage, VersionError, Vrsn_1_0, Vrsn_2_0 +from keri.kering import Protocols, Protocolage, Ilkage, Ilks, TraitDex from keri.core import coring from keri.core import eventing -from keri.core.coring import (Ilkage, Ilks, Saids, Protocols, Protocolage, - Sadder, Tholder, Seqner, - NumDex, Number, Siger, Dater, Bexter, Texter, - Verser, Versage, TagDex, PadTagDex, Tagger) +from keri.core.coring import (Saids, Sadder, Tholder, Seqner, NumDex, Number, + Siger, Dater, Bexter, Texter, + TagDex, PadTagDex, Tagger, Ilker, Traitor, + Verser, Versage, ) from keri.core.coring import Serialage, Serials, Tiers from keri.core.coring import (Sizage, MtrDex, Matter, Xizage, IdrDex, IdxSigDex, IdxCrtSigDex, IdxBthSigDex, Indexer, @@ -44,7 +45,7 @@ from keri.core.coring import (Verfer, Cigar, Signer, Salter, Saider, DigDex, Diger, Prefixer, Cipher, Encrypter, Decrypter) from keri.core.coring import versify, deversify, Rever, MAXVERFULLSPAN -from keri.core.coring import generateSigners, generatePrivates +from keri.core.coring import generateSigners from keri.help import helping from keri.help.helping import (sceil, intToB64, intToB64b, b64ToInt, codeB64ToB2, codeB2ToB64, @@ -4174,9 +4175,6 @@ def test_tagger(): """ # Test TagCodex PadTagCodex and associated Sizes to be valid specials - - - with pytest.raises(EmptyMaterialError): tagger = Tagger() # defaults @@ -4219,7 +4217,6 @@ def test_tagger(): assert tagger.composable assert tagger.tag == tag - tagger = Tagger(qb64b=qb64b) assert tagger.code == tagger.hard == code assert tagger.soft == soft @@ -4257,6 +4254,194 @@ def test_tagger(): """ Done Test """ +def test_ilker(): + """ + Test Ilker message type subclass of Tagger + """ + with pytest.raises(EmptyMaterialError): + ilker = Ilker() # defaults + + ilk = Ilks.rot + tag = ilk + code = MtrDex.Tag3 + soft = 'rot' + qb64 = 'Xrot' + qb64b = qb64.encode("utf-8") + qb2 = decodeB64(qb64b) + raw = b'' + + ilker = Ilker(ilk=ilk) # defaults + assert ilker.code == ilker.hard == code + assert ilker.soft == soft + assert ilker.raw == raw + assert ilker.qb64 == qb64 + assert ilker.qb2 == qb2 + assert ilker.special + assert ilker.composable + assert ilker.tag == tag + assert ilker.ilk == ilk + + ilker = Ilker(qb2=qb2) + assert ilker.code == ilker.hard == code + assert ilker.soft == soft + assert ilker.raw == raw + assert ilker.qb64 == qb64 + assert ilker.qb2 == qb2 + assert ilker.special + assert ilker.composable + assert ilker.tag == tag + assert ilker.ilk == ilk + + ilker = Ilker(qb64=qb64) + assert ilker.code == ilker.hard == code + assert ilker.soft == soft + assert ilker.raw == raw + assert ilker.qb64 == qb64 + assert ilker.qb2 == qb2 + assert ilker.special + assert ilker.composable + assert ilker.tag == tag + assert ilker.ilk == ilk + + ilker = Ilker(qb64b=qb64b) + assert ilker.code == ilker.hard == code + assert ilker.soft == soft + assert ilker.raw == raw + assert ilker.qb64 == qb64 + assert ilker.qb2 == qb2 + assert ilker.special + assert ilker.composable + assert ilker.tag == tag + assert ilker.ilk == ilk + + ilker = Ilker(tag=tag) + assert ilker.code == ilker.hard == code + assert ilker.soft == soft + assert ilker.raw == raw + assert ilker.qb64 == qb64 + assert ilker.qb2 == qb2 + assert ilker.special + assert ilker.composable + assert ilker.tag == tag + assert ilker.ilk == ilk + + # test error condition + with pytest.raises(InvalidSoftError): + ilker = Ilker(ilk='bad') + + # ignores code + ilker = Ilker(ilk=ilk, code=MtrDex.Tag4) + assert ilker.code == ilker.hard == code + assert ilker.soft == soft + assert ilker.raw == raw + assert ilker.qb64 == qb64 + assert ilker.qb2 == qb2 + assert ilker.special + assert ilker.composable + assert ilker.tag == tag + assert ilker.ilk == ilk + + # test error using soft and code + with pytest.raises(InvalidCodeError): + ilker = Ilker(soft='bady', code=MtrDex.Tag4) + + """End Test""" + + +def test_traitor(): + """ + Test Traitor configuration trait subclass of Tagger + """ + with pytest.raises(EmptyMaterialError): + traitor = Traitor() # defaults + + trait = TraitDex.EstOnly + tag = trait + code = MtrDex.Tag2 + soft = 'EO' + qb64 = '0KEO' + qb64b = qb64.encode("utf-8") + qb2 = decodeB64(qb64b) + raw = b'' + + traitor = Traitor(trait=trait) # defaults + assert traitor.code == traitor.hard == code + assert traitor.soft == soft + assert traitor.raw == raw + assert traitor.qb64 == qb64 + assert traitor.qb2 == qb2 + assert traitor.special + assert traitor.composable + assert traitor.tag == tag + assert traitor.trait == trait + + traitor = Traitor(qb2=qb2) + assert traitor.code == traitor.hard == code + assert traitor.soft == soft + assert traitor.raw == raw + assert traitor.qb64 == qb64 + assert traitor.qb2 == qb2 + assert traitor.special + assert traitor.composable + assert traitor.tag == tag + assert traitor.trait == trait + + traitor = Traitor(qb64=qb64) + assert traitor.code == traitor.hard == code + assert traitor.soft == soft + assert traitor.raw == raw + assert traitor.qb64 == qb64 + assert traitor.qb2 == qb2 + assert traitor.special + assert traitor.composable + assert traitor.tag == tag + assert traitor.trait == trait + + traitor = Traitor(qb64b=qb64b) + assert traitor.code == traitor.hard == code + assert traitor.soft == soft + assert traitor.raw == raw + assert traitor.qb64 == qb64 + assert traitor.qb2 == qb2 + assert traitor.special + assert traitor.composable + assert traitor.tag == tag + assert traitor.trait == trait + + traitor = Traitor(tag=tag) + assert traitor.code == traitor.hard == code + assert traitor.soft == soft + assert traitor.raw == raw + assert traitor.qb64 == qb64 + assert traitor.qb2 == qb2 + assert traitor.special + assert traitor.composable + assert traitor.tag == tag + assert traitor.trait == trait + + # test error condition + with pytest.raises(InvalidSoftError): + traitor = Traitor(trait='bad') + + # ignores code + traitor = Traitor(trait=trait, code=MtrDex.Tag4) + assert traitor.code == traitor.hard == code + assert traitor.soft == soft + assert traitor.raw == raw + assert traitor.qb64 == qb64 + assert traitor.qb2 == qb2 + assert traitor.special + assert traitor.composable + assert traitor.tag == tag + assert traitor.trait == trait + + # test error using soft and code + with pytest.raises(InvalidSoftError): + traitor = Traitor(soft='bady', code=MtrDex.Tag4) + + """End Test""" + + def test_verser(): """ Test Verser version primitive subclass of Matter @@ -5721,10 +5906,10 @@ def test_generatesigners(): for signer in signers: assert signer.verfer.code == MtrDex.Ed25519N - # salt = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES) - salt = b'g\x15\x89\x1a@\xa4\xa47\x07\xb9Q\xb8\x18\xcdJW' - assert len(salt) == 16 - signers = generateSigners(salt=salt, count=4) # default is transferable + # raw = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES) # raw salt + raw = b'g\x15\x89\x1a@\xa4\xa47\x07\xb9Q\xb8\x18\xcdJW' + assert len(raw) == 16 + signers = generateSigners(raw=raw, count=4) # default is transferable assert len(signers) == 4 for signer in signers: assert signer.code == MtrDex.Ed25519_Seed @@ -5736,9 +5921,6 @@ def test_generatesigners(): 'AHMBU5PsIJN2U9m7j0SGyvs8YD8fkym2noELzxIrzfdG', 'AJZ7ZLd7unQ4IkMUwE69NXcvDO9rrmmRH_Xk3TPu9BpP'] - secrets = generatePrivates(salt=salt, count=4) - assert secrets == sigkeys - """ End Test """ @@ -7155,6 +7337,8 @@ def test_tholder(): test_matter() test_matter_special() test_tagger() + test_ilker() + test_traitor() test_verser() #test_texter() #test_counter() diff --git a/tests/core/test_eventing.py b/tests/core/test_eventing.py index 785b83fca..d55f35e17 100644 --- a/tests/core/test_eventing.py +++ b/tests/core/test_eventing.py @@ -2451,7 +2451,7 @@ def test_keyeventsequence_0(): """ # create signers salt = b'g\x15\x89\x1a@\xa4\xa47\x07\xb9Q\xb8\x18\xcdJW' - signers = generateSigners(salt=salt, count=8, transferable=True) + signers = generateSigners(raw=salt, count=8, transferable=True) pubkeys = [signer.verfer.qb64 for signer in signers] assert pubkeys == ['DErocgXD2RGSyvn3MObcx59jeOsEQhv2TqHirVkzrp0Q', @@ -2721,7 +2721,7 @@ def test_keyeventsequence_1(): # create signers salt = b'g\x15\x89\x1a@\xa4\xa47\x07\xb9Q\xb8\x18\xcdJW' - signers = generateSigners(salt=salt, count=8, transferable=True) + signers = generateSigners(raw=salt, count=8, transferable=True) pubkeys = [signer.verfer.qb64 for signer in signers] assert pubkeys == ['DErocgXD2RGSyvn3MObcx59jeOsEQhv2TqHirVkzrp0Q', @@ -2818,7 +2818,7 @@ def test_multisig_digprefix(): # create signers salt = b'g\x15\x89\x1a@\xa4\xa47\x07\xb9Q\xb8\x18\xcdJW' - signers = generateSigners(salt=salt, count=8, transferable=True) + signers = generateSigners(raw=salt, count=8, transferable=True) pubkeys = [signer.verfer.qb64 for signer in signers] assert pubkeys == ['DErocgXD2RGSyvn3MObcx59jeOsEQhv2TqHirVkzrp0Q', @@ -2972,7 +2972,7 @@ def test_recovery(): """ # create signers salt = b'g\x15\x89\x1a@\xa4\xa47\x07\xb9Q\xb8\x18\xcdJW' - signers = generateSigners(salt=salt, count=8, transferable=True) + signers = generateSigners(raw=salt, count=8, transferable=True) with openDB(name="controller") as conlgr, openDB(name="validator") as vallgr: event_digs = [] # list of event digs in sequence to verify against database diff --git a/tests/core/test_serdering.py b/tests/core/test_serdering.py index 421284148..1ded13d64 100644 --- a/tests/core/test_serdering.py +++ b/tests/core/test_serdering.py @@ -23,6 +23,10 @@ from keri.core.serdering import (FieldDom, FieldDom, Serdery, Serder, SerderKERI, SerderACDC, ) +from keri.core.eventing import (incept, ) + +from keri.app import habbing + def test_fielddom(): """Test FieldDom dataclass""" @@ -2637,6 +2641,95 @@ def test_serdery(): """End Test""" +def test_cesr_native_dumps(): + """Test Serder._dumps""" + + # use same salter for all but different path + # salt = pysodium.randombytes(pysodium.crypto_pwhash_SALTBYTES) + raw = b'\x05\xaa\x8f-S\x9a\xe9\xfaU\x9c\x02\x9c\x9b\x08Hu' + salter = coring.Salter(raw=raw) + + csigners = coring.generateSigners(raw=salter.raw, count=3) + wsigners = coring.generateSigners(raw=salter.raw, count=3, transferable=False) + + + keys = ["EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf87Bc70J"] + serder = incept(keys, version=Vrsn_2_0) + + assert serder.sad == \ + { + 'v': 'KERICAAJSONAAD8.', + 't': 'icp', + 'd': 'EF_SoHnCdQ0N9Kivxl54u3l1-sKwDL0gs729_REO6koi', + 'i': 'EF_SoHnCdQ0N9Kivxl54u3l1-sKwDL0gs729_REO6koi', + 's': '0', + 'kt': '1', + 'k': ['EDGnGYIa5obfFUhxcAuUmM4fJyeRYj2ti3KGf87Bc70J'], + 'nt': '0', + 'n': [], + 'bt': '0', + 'b': [], + 'c': [], + 'a': [] + } + + salt = salter.qb64 + assert salt == '0AAFqo8tU5rp-lWcApybCEh1' + + # need to fix this so it uses different Kind and different Version + # makHab uses stem=name to make different names have differnt AID pre + with (habbing.openHby(name="wes", base="test", salt=salt) as wesHby, + habbing.openHby(name="wok", base="test", salt=salt) as wokHby, + habbing.openHby(name="wam", base="test", salt=salt) as wamHby, + habbing.openHby(name="cam", base="test", salt=salt) as camHby): + + # witnesses first so can setup inception event for tam + wsith = '1' + wesHab = wesHby.makeHab(name='wes', isith=wsith, icount=1, transferable=False) + wokHab = wokHby.makeHab(name='wok', isith=wsith, icount=1, transferable=False) + wamHab = wamHby.makeHab(name='wam', isith=wsith, icount=1, transferable=False) + + # setup Tam's habitat trans multisig + wits = [wesHab.pre, wokHab.pre, wamHab.pre] + tsith = '2' # hex str of threshold int + camHab = camHby.makeHab(name='cam', isith=tsith, icount=3, toad=2, wits=wits,) + + assert camHab.kever.prefixer.transferable + assert len(camHab.iserder.berfers) == len(wits) + for werfer in camHab.iserder.berfers: + assert werfer.qb64 in wits + assert camHab.kever.wits == wits + assert camHab.kever.toader.num == 2 + assert camHab.kever.sn == 0 + assert camHab.kever.tholder.thold == 2 == int(tsith, 16) + + serder, _, _ = camHab.getOwnEvent(sn=0) + + assert serder.sad == \ + { + 'v': 'KERI10JSON000273_', + 't': 'icp', + 'd': 'ED7ek7qhzr9SzqmV8IBxgHHWfsNcbWd-CKHG4-mHua6e', + 'i': 'ED7ek7qhzr9SzqmV8IBxgHHWfsNcbWd-CKHG4-mHua6e', + 's': '0', + 'kt': '2', + 'k': ['DJV4r5kpA-DuQGmDr3owzHvcWreg9fWetlS_hoznje4Q', + 'DHjp2Ewj88Url6d23i6myE-c3bSjOuNgjkZKnF8LkH7C', + 'DDjY_8DygjZg6F5-qWfZahKwPHjs1gSjzGU6nqikn1g0'], + 'nt': '2', + 'n': ['EHY_zOKIFva_iS1bGuu2etyuQOuq3tOrjaRIYHknRSSz', + 'ENlS_9WEDDgjpVRmux37ITU4O6UW8hOif-Gwa3Ch0I6t', + 'EJ67YtK72WQBmGSLS1ibDIVGM4hHtf2HrTPd1Mn51iWV'], + 'bt': '2', + 'b': ['BBVDlgWic_rAf-m_v7vz_VvIYAUPErvZgLTfXGNrFRom', + 'BKVb58uITf48YoMPz8SBOTVwLgTO9BY4oEXRPoYIOErX', + 'BByq5Nfi0KgohEaJ8h9JrLqbhX_waySFSXKsgumxEYQp'], + 'c': [], + 'a': [] + } + + """End Test""" + if __name__ == "__main__": @@ -2659,4 +2752,5 @@ def test_serdery(): test_serderacdc() test_serder_v2() test_serdery() + test_cesr_native_dumps() diff --git a/tests/test_kering.py b/tests/test_kering.py index 4041bced0..aeb86e717 100644 --- a/tests/test_kering.py +++ b/tests/test_kering.py @@ -5,6 +5,7 @@ """ import re import json +from dataclasses import asdict, astuple import cbor2 as cbor import msgpack @@ -15,19 +16,20 @@ from keri.kering import Protocolage, Protocols from keri.kering import Serialage, Serials from keri.kering import Ilkage, Ilks +from keri.kering import ColdCodex, ColdDex, TraitCodex, TraitDex from keri.kering import (Versionage, Version, MAXVERFULLSPAN, versify, deversify, Rever, Smellage, smell, VER1FULLSPAN, VER1TERM, VEREX1, VER2FULLSPAN, VER2TERM, VEREX2, VEREX) -from keri.kering import VFFULLSPAN, VFREX, Revfer + from keri.kering import VersionError, ProtocolError, KindError from keri.help.helping import (intToB64, intToB64b, b64ToInt, B64_CHARS, codeB64ToB2, codeB2ToB64, Reb64, nabSextets) -def test_protos(): +def test_protocols(): """ Test protocols namedtuple instance Protocols """ @@ -207,6 +209,86 @@ def test_snuff(): """ + + # version field in CESR native serialization + VFFULLSPAN = 12 # number of characters in full version string + VFREX = b'0N(?P[A-Z]{4})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})(?P[0-9A-Za-z_-])(?P[0-9A-Za-z_-]{2})' + + Revfer = re.compile(VFREX) # compile is faster + + MAXVFOFFSET = 12 + + SNUFFSIZE = MAXVFOFFSET + VFFULLSPAN + + def snatch(match, size=0): + """ Returns: + smellage (Smellage): named tuple extracted from version string regex match + (protocol, version, kind, size) + + Parameters: + match (re.Match): instance of Match class + size (int): provided size to substitute when missing + + Notes: + regular expressions work with memoryview objects not just bytes or + bytearrays + """ + full = match.group() # full matched version string + if len(full) == VFFULLSPAN: + proto, major, minor, gmajor, gminor = match.group("proto0", + "major0", + "minor0", + "gmajor0", + "gminor0") + proto = proto.decode("utf-8") + if proto not in Protocols: + raise ProtocolError(f"Invalid protocol type = {proto}.") + vrsn = Versionage(major=b64ToInt(major), minor=b64ToInt(minor)) + if vrsn.major < 2: # version2 vs but major < 2 + raise VersionError(f"Incompatible {vrsn=} with version string.") + + gvrsn = Versionage(major=b64ToInt(gmajor), minor=b64ToInt(gminor)) + if gvrsn.major < 2: # version2 vs but major < 2 + raise VersionError(f"Incompatible {gvrsn=} with CESR native version" + f"field.") + kind = Serials.cesr + size = size + else: + raise VersionError(f"Bad snatch.") + + return Smellage(proto=proto, vrsn=vrsn, kind=kind, size=size, gvrsn=gvrsn) + + + def snuff(raw, size=0): + """Extract and return instance of Smellage from version string inside + raw serialization. + + Returns: + smellage (Smellage): named Tuple of (protocol, version, kind, size) + + Parameters: + raw (bytearray) of serialized incoming message stream. Assumes start + of stream is JSON, CBOR, or MGPK field map with first field + is labeled 'v' and value is version string. + size (int): provided size to substitute when missing + + """ + if len(raw) < SNUFFSIZE: + raise kering.ShortageError(f"Need more raw bytes to smell full version string.") + + match = Rever.search(raw) # Rever regex takes bytes/bytearray not str + if not match or match.start() > MAXVFOFFSET: + raise kering.VersionError(f"Invalid version string from smelled raw = " + f"{raw[: SNUFFSIZE]}.") + + return snatch(match, size=size) + + + + + + + #pattern = re.compile(VFREX) # compile is faster pattern = Revfer @@ -690,7 +772,35 @@ def test_versify_v2(): """End Test""" +def test_colddex(): + """ + Test ColdDex instance of ColdCodex dataclass + """ + assert isinstance(ColdDex, ColdCodex) + + assert asdict(ColdDex) == \ + { + 'AnB64': 0, + 'CtB64': 1, + 'OpB64': 2, + 'JSON': 3, + 'MGPK1': 4, + 'CBOR': 5, + 'MGPK2': 6, + 'CtOpB2': 7 + } + + assert 0o0 in ColdDex + assert 0o1 in ColdDex + assert 0o2 in ColdDex + assert 0o3 in ColdDex + assert 0o4 in ColdDex + assert 0o5 in ColdDex + assert 0o6 in ColdDex + assert 0o7 in ColdDex + + """End Test""" def test_ilks(): @@ -770,11 +880,37 @@ def test_ilks(): """End Test """ +def test_traitdex(): + """ + Test TraitDex instance of TraitCodex dataclass + """ + + assert isinstance(TraitDex, TraitCodex) + + assert asdict(TraitDex) == \ + { + 'EstOnly': 'EO', + 'DoNotDelegate': 'DND', + 'RegistrarBackers': 'RB', + 'NoBackers': 'NB', + 'NoRegistrarBackers': 'NRB', + 'DelegateIsDelegator': 'DID', + } + + assert 'EO' in TraitDex + assert 'DND' in TraitDex + assert 'RB' in TraitDex + assert 'NB' in TraitDex + assert 'NRB' in TraitDex + assert 'DID' in TraitDex + + """End Test""" if __name__ == "__main__": - test_protos() + test_protocols() + test_version_regex() test_smell() test_snuff() @@ -782,4 +918,5 @@ def test_ilks(): test_versify_v1() test_versify_v2() test_ilks() - + test_colddex() + test_traitdex() diff --git a/tests/vdr/test_eventing.py b/tests/vdr/test_eventing.py index 079acc328..3da4f055c 100644 --- a/tests/vdr/test_eventing.py +++ b/tests/vdr/test_eventing.py @@ -37,7 +37,7 @@ def test_incept(mockCoringRandomNonce): b'fpxIl1LcIkMhUSCCC8fgvkuX8gG9xK3SM-S8a8Y_U"}') # no backers allowed - serder = eventing.incept(pre, baks=[], cnfg=[keventing.TraitDex.NoRegistrarBackers], code=MtrDex.Blake3_256) + serder = eventing.incept(pre, baks=[], cnfg=[keventing.TraitDex.NoBackers], code=MtrDex.Blake3_256) assert serder.raw == (b'{"v":"KERI10JSON000113_","t":"vcp","d":"EBoBPh3N5nr1tItAUCkXNx3vShB_Be6iiQPX' b'Bsg2LvxA","i":"EBoBPh3N5nr1tItAUCkXNx3vShB_Be6iiQPXBsg2LvxA","ii":"DAtNTPnDF' b'BnmlO6J44LXCrzZTAmpe-82b7BmQGtL4QhM","s":"0","c":["NB"],"bt":"0","b":[],"n":' @@ -45,7 +45,7 @@ def test_incept(mockCoringRandomNonce): # no backers allows, one attempted with pytest.raises(ValueError): - eventing.incept(pre, cnfg=[keventing.TraitDex.NoRegistrarBackers], + eventing.incept(pre, cnfg=[keventing.TraitDex.NoBackers], baks=[bak1]) # with backer dupes @@ -368,7 +368,7 @@ def test_prefixer(): t=Ilks.vcp, bt=0, b=[], - c=[keventing.TraitDex.NoRegistrarBackers], + c=[keventing.TraitDex.NoBackers], ) prefixer = Prefixer(ked=ked, code=MtrDex.Blake3_256) assert prefixer.qb64 == 'EDz0QmMxf4Dk0C9uiP-y3okN-Bej2IAXSj8UwQgb3NsL'