Source code for keri.core.counting

# -*- coding: utf-8 -*-
"""
keri.core.counting module

Provides versioning support for Counter classes and codes
"""
import copy

from dataclasses import dataclass, astuple, asdict
from collections import namedtuple

from ..help import (sceil, intToB64, b64ToInt, codeB64ToB2, codeB2ToB64, Reb64,
                    nabSextets)

from ..kering import (Colds, Versionage, Vrsn_1_0, Vrsn_2_0, InvalidVersionError,
                      InvalidCodeError, InvalidCodeSizeError, InvalidVarIndexError,
                      EmptyMaterialError, ShortageError, UnexpectedOpCodeError,
                      UnexpectedCodeError)

from .coring import IceMapDom


[docs] @dataclass(frozen=True) class GenusCodex(IceMapDom): """GenusCodex is codex of protocol genera for code table. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ KERI: str = '-_AAA' # KERI Tables may be shared by ACDC and SPAC and TSP_ ACDC: str = '-_AAB' # Reserved in case ACDC can no longer share with KERI SPAC: str = '-_AAC' # Reserved in case SPAC can no longer share with KERI TSP_: str = '-_AAD' # Reserved in case TSP_ can no longer share with KERI def __iter__(self): return iter(astuple(self)) # enables inclusion test with "in"
# duplicate values above just result in multiple entries in tuple so # in inclusion still works GenDex = GenusCodex() # Make instance # maps Protocol code from Protocols = Protocolage(keri="KERI", acdc="ACDC") # to Genus Name in GenDex so can lookup chared CESR Genus code from Protocol Code # Usage: genus code = GenDex[ProGen["ACDC"]] == '-_AAA' ProGen = dict(KERI='KERI', ACDC='KERI')
[docs] @dataclass(frozen=True) class CounterCodex_1_0(IceMapDom): """CounterCodex_1_0 is codex hard (stable) part of all V1 counter codes. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ ControllerIdxSigs: str = '-A' # Qualified Base64 Indexed Signature. WitnessIdxSigs: str = '-B' # Qualified Base64 Indexed Signature. NonTransReceiptCouples: str = '-C' # Composed Base64 Couple, pre+cig. TransReceiptQuadruples: str = '-D' # Composed Base64 Quadruple, pre+snu+dig+sig. FirstSeenReplayCouples: str = '-E' # Composed Base64 Couple, fnu+dts. TransIdxSigGroups: str = '-F' # Composed Base64 Group, pre+snu+dig+ControllerIdxSigs group. SealSourceCouples: str = '-G' # Composed Base64 couple, snu+dig of given delegator/issuer/transaction event TransLastIdxSigGroups: str = '-H' # Composed Base64 Group, pre+ControllerIdxSigs group. SealSourceTriples: str = '-I' # Composed Base64 triple, pre+snu+dig of anchoring source event PathedMaterialCouples: str = '-L' # Composed Grouped Pathed Material Quadlet (4 char each) BigPathedMaterialCouples: str = '--L' # Composed Grouped Pathed Material Quadlet (4 char each) GenericGroup: str = '-T' # Generic Material Quadlet (Universal with override) BigGenericGroup: str = '--T' # Big Generic Material Quadlet (Universal with override) BodyWithAttachmentGroup: str = '-U' # Message Body plus Attachments Quadlet (Universal with Override). BigBodyWithAttachmentGroup: str = '--U' # Big Message Body plus Attachments Quadlet (Universal with Override) AttachmentGroup: str = '-V' # Message Attachments Only Quadlet (Universal with Override) BigAttachmentGroup: str = '--V' # Message Attachments Only Quadlet (Universal with Override) NonNativeBodyGroup: str = '-W' # Message body Non-native enclosed with Texter BigNonNativeBodyGroup: str = '--W' # Big Message body Non-native enclosed with Texter ESSRPayloadGroup: str = '-Z' # ESSR Payload Group Quadlets (not implemented as quadlets) BigESSRPayloadGroup: str = '--Z' # Big ESSR Payload Group Quadlets (not implemented as quadlets) KERIACDCGenusVersion: str = '-_AAA' # KERI ACDC Protocol Stack CESR Version def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
CtrDex_1_0 = CounterCodex_1_0()
[docs] @dataclass(frozen=True) class QuadTripCodex_1_0(IceMapDom): """QuadTripCodex_1_0 is codex hard (stable) part of all V1 counter codes that count quadlets/triplets. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ PathedMaterialCouples: str = '-L' # Composed Grouped Pathed Material Quadlet (4 char each) BigPathedMaterialCouples: str = '--L' # Composed Grouped Pathed Material Quadlet (4 char each) GenericGroup: str = '-T' # Generic Material Quadlet (Universal with override) BigGenericGroup: str = '--T' # Big Generic Material Quadlet (Universal with override) BodyWithAttachmentGroup: str = '-U' # Message Body plus Attachments Quadlet (Universal with Override). BigBodyWithAttachmentGroup: str = '--U' # Big Message Body plus Attachments Quadlet (Universal with Override) AttachmentGroup: str = '-V' # Message Attachments Only Quadlet (Universal with Override) BigAttachmentGroup: str = '--V' # Message Attachments Only Quadlet (Universal with Override) NonNativeBodyGroup: str = '-W' # Message body Non-native enclosed with Texter BigNonNativeBodyGroup: str = '--W' # Big Message body Non-native enclosed with Texter ESSRPayloadGroup: str = '-Z' # ESSR Payload Group Quadlets (not implemented as quadlets) BigESSRPayloadGroup: str = '--Z' # Big ESSR Payload Group Quadlets (not implemented as quadlets) def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
QTDex_1_0 = QuadTripCodex_1_0()
[docs] @dataclass(frozen=True) class UniversalCodex_1_0(IceMapDom): """CounterCodex_1_0 is codex hard (stable) part of all V1 universal counter codes. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ GenericGroup: str = '-T' # Generic Material Quadlet (Universal with override) BigGenericGroup: str = '--T' # Big Generic Material Quadlet (Universal with override) BodyWithAttachmentGroup: str = '-U' # Message Body plus Attachments Quadlet (Universal with Override). BigBodyWithAttachmentGroup: str = '--U' # Big Message Body plus Attachments Quadlet (Universal with Override) AttachmentGroup: str = '-V' # Message Attachments Only Quadlet (Universal with Override) BigAttachmentGroup: str = '--V' # Message Attachments Only Quadlet (Universal with Override) NonNativeBodyGroup: str = '-W' # Message body Non-native enclosed with Texter BigNonNativeBodyGroup: str = '--W' # Big Message body Non-native enclosed with Texter KERIACDCGenusVersion: str = '-_AAA' # KERI ACDC Stack CESR Protocol Genus Version (Universal) def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
UniDex_1_0 = UniversalCodex_1_0()
[docs] @dataclass(frozen=True) class SpecialUniversalCodex_1_0(IceMapDom): """SpecialUniversalCodex_1_0 is codex hard (stable) part of all V1 special universal counter codes that may have optional genus-version override as first code in enclosed group. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ GenericGroup: str = '-T' # Generic Material Quadlet (Universal with override) BigGenericGroup: str = '--T' # Big Generic Material Quadlet (Universal with override) BodyWithAttachmentGroup: str = '-U' # Message Body plus Attachments Quadlet (Universal with Override). BigBodyWithAttachmentGroup: str = '--U' # Big Message Body plus Attachments Quadlet (Universal with Override) AttachmentGroup: str = '-V' # Message Attachments Only Quadlet (Universal with Override) BigAttachmentGroup: str = '--V' # Message Attachments Only Quadlet (Universal with Override) def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
SUDex_1_0 = SpecialUniversalCodex_1_0()
[docs] @dataclass(frozen=True) class MessageUniversalCodex_1_0(IceMapDom): """MessageUniversalCodex_1_0 is codex hard (stable) part of all V1 message universal counter codes that support CESR native messages. (currently none) But needed for symmetry when changing versions in how lookup happens in parser. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ NonNativeBodyGroup: str = '-W' # Message body Non-native enclosed with Texter BigNonNativeBodyGroup: str = '--W' # Big Message body Non-native enclosed with Texter def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
MUDex_1_0 = MessageUniversalCodex_1_0()
[docs] @dataclass(frozen=True) class CounterCodex_2_0(IceMapDom): """CounterCodex_2_0 is codex hard (stable) part of all V2 counter codes. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ GenericGroup: str = '-A' # Generic Group (Universal with Override). BigGenericGroup: str = '--A' # Big Generic Group (Universal with Override). BodyWithAttachmentGroup: str = '-B' # Message Body plus Attachments Group (Universal with Override). BigBodyWithAttachmentGroup: str = '--B' # Big Message Body plus Attachments Group (Universal with Override). AttachmentGroup: str = '-C' # Message Attachments Only Group (Universal with Override). BigAttachmentGroup: str = '--C' # Big Attachments Only Group (Universal with Override). DatagramSegmentGroup: str = '-D' # Datagram Segment Group (Universal). BigDatagramSegmentGroup: str = '--D' # Big Datagram Segment Group (Universal). ESSRWrapperGroup: str = '-E' # ESSR Wrapper Group (Universal). BigESSRWrapperGroup: str = '--E' # Big ESSR Wrapper Group (Universal). FixBodyGroup: str = '-F' # Fixed Field Message Body Group (Universal). BigFixBodyGroup: str = '--F' # Big Fixed Field Message Body Group (Universal). MapBodyGroup: str = '-G' # Field Map Message Body Group (Universal). BigMapBodyGroup: str = '--G' # Big Field Map Message Body Group (Universal). NonNativeBodyGroup: str = '-H' # Message body Non-native enclosed with Texter BigNonNativeBodyGroup: str = '--H' # Big Message body Non-native enclosed with Texter GenericMapGroup: str = '-I' # Generic Field Map Group (Universal). BigGenericMapGroup: str = '--I' # Big Generic Field Map Group (Universal). GenericListGroup: str = '-J' # Generic List Group (Universal). BigGenericListGroup: str = '--J' # Big Generic List Group (Universal). ControllerIdxSigs: str = '-K' # Controller Indexed Signature(s) of qb64. BigControllerIdxSigs: str = '--K' # Big Controller Indexed Signature(s) of qb64. WitnessIdxSigs: str = '-L' # Witness Indexed Signature(s) of qb64. BigWitnessIdxSigs: str = '--L' # Big Witness Indexed Signature(s) of qb64. NonTransReceiptCouples: str = '-M' # NonTrans Receipt Couple(s), pre+cig. BigNonTransReceiptCouples: str = '--M' # Big NonTrans Receipt Couple(s), pre+cig. TransReceiptQuadruples: str = '-N' # Trans Receipt Quadruple(s), pre+snu+dig+sig. BigTransReceiptQuadruples: str = '--N' # Big Trans Receipt Quadruple(s), pre+snu+dig+sig. FirstSeenReplayCouples: str = '-O' # First Seen Replay Couple(s), fnu+dts. BigFirstSeenReplayCouples: str = '--O' # First Seen Replay Couple(s), fnu+dts. PathedMaterialCouples: str = '-P' # Pathed Material couples. path+text BigPathedMaterialCouples: str = '--P' # Big Pathed Material couples. path+text DigestSealSingles: str = '-Q' # Digest Seal Single(s), dig of sealed data. BigDigestSealSingles: str = '--Q' # Big Digest Seal Single(s), dig of sealed data. MerkleRootSealSingles: str = '-R' # Merkle Tree Root Digest Seal Single(s), dig of sealed data. BigMerkleRootSealSingles: str = '--R' # Merkle Tree Root Digest Seal Single(s), dig of sealed data. SealSourceCouples: str = '-S' # Seal Source Couple(s), snu+dig of source sealing or sealed event. BigSealSourceCouples: str = '--S' # Seal Source Couple(s), snu+dig of source sealing or sealed event. SealSourceTriples: str = '-T' # Seal Source Triple(s), pre+snu+dig of source sealing or sealed event. BigSealSourceTriples: str = '--T' # Seal Source Triple(s), pre+snu+dig of source sealing or sealed event. SealSourceLastSingles: str = '-U' # Seal Source Couple(s), pre of last source sealing or sealed event. BigSealSourceLastSingles: str = '--U' # Big Seal Source Couple(s), pre of last source sealing or sealed event. BackerRegistrarSealCouples: str = '-V' # Backer Registrar Seal Couple(s), brid+dig of sealed data. BigBackerRegistrarSealCouples: str = '--V' # Big Backer Registrar Seal Couple(s), brid+dig of sealed data. TypedDigestSealCouples: str = '-W' # Typed Digest Seal Couple(s), type seal vers+dig of sealed data. BigTypedDigestSealCouples: str = '--W' # Big Typed Digest Seal Couple(s), type seal vers+dig of sealed data. TransIdxSigGroups: str = '-X' # Trans Indexed Signature Group(s), pre+snu+dig+CtrControllerIdxSigs of qb64. BigTransIdxSigGroups: str = '--X' # Big Trans Indexed Signature Group(s), pre+snu+dig+CtrControllerIdxSigs of qb64. TransLastIdxSigGroups: str = '-Y' # Trans Last Est Evt Indexed Signature Group(s), pre+CtrControllerIdxSigs of qb64. BigTransLastIdxSigGroups: str = '--Y' # Big Trans Last Est Evt Indexed Signature Group(s), pre+CtrControllerIdxSigs of qb64. ESSRPayloadGroup: str = '-Z' # ESSR Payload Group. BigESSRPayloadGroup: str = '--Z' # Big ESSR Payload Group. BlindedStateQuadruples: str = '-a' # Blinded transaction event state quadruples blid+uuid+said+state. BigBlindedStateQuadruples: str = '--a' # Big Blinded transaction event state quadruples blid+uuid+said+state. BoundStateSextuples: str = '-b' # Bound Blinded transaction event state sextuples blid+uuid+said+state+bsnu+bsaid. BigBoundStateSextuples: str = '--b' # Big Bound Blinded transaction event state sextuples blid+uuid+said+state+bsnu+bsaid. TypedMediaQuadruples: str = '-c' # Typed and Blinded IANA media type quadruples blid+uuid+type+media BigTypedMediaQuadruples: str = '--c' # Big Type and Blinded IANA media type quadruples blid+uuid+type+media KERIACDCGenusVersion: str = '-_AAA' # KERI ACDC Stack CESR Protocol Genus Version (Universal) def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
CtrDex_2_0 = CounterCodex_2_0()
[docs] @dataclass(frozen=True) class UniversalCodex_2_0(IceMapDom): """CounterCodex_2_0 is codex hard (stable) part of all V2 universal counter codes. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ GenericGroup: str = '-A' # Generic Group (Universal with Override). BigGenericGroup: str = '--A' # Big Generic Group (Universal with Override). BodyWithAttachmentGroup: str = '-B' # Message Body plus Attachments Group (Universal with Override). BigBodyWithAttachmentGroup: str = '--B' # Big Message Body plus Attachments Group (Universal with Override). AttachmentGroup: str = '-C' # Message Attachments Only Group (Universal with Override). BigAttachmentGroup: str = '--C' # Big Attachments Only Group (Universal with Override). DatagramSegmentGroup: str = '-D' # Datagram Segment Group (Universal). BigDatagramSegmentGroup: str = '--D' # Big Datagram Segment Group (Universal). ESSRWrapperGroup: str = '-E' # ESSR Wrapper Group (Universal). BigESSRWrapperGroup: str = '--E' # Big ESSR Wrapper Group (Universal). FixBodyGroup: str = '-F' # Fixed Field Message Body Group (Universal). BigFixBodyGroup: str = '--F' # Big Fixed Field Message Body Group (Universal). MapBodyGroup: str = '-G' # Field Map Message Body Group (Universal). BigMapBodyGroup: str = '--G' # Big Field Map Message Body Group (Universal). NonNativeBodyGroup: str = '-H' # Message body Non-native enclosed with Texter BigNonNativeBodyGroup: str = '--H' # Big Message body Non-native enclosed with Texter GenericMapGroup: str = '-I' # Generic Field Map Group (Universal). BigGenericMapGroup: str = '--I' # Big Generic Field Map Group (Universal). GenericListGroup: str = '-J' # Generic List Group (Universal). BigGenericListGroup: str = '--J' # Big Generic List Group (Universal). KERIACDCGenusVersion: str = '-_AAA' # KERI ACDC Stack CESR Protocol Genus Version (Universal) def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
UniDex_2_0 = UniversalCodex_2_0()
[docs] @dataclass(frozen=True) class SpecialUniversalCodex_2_0(IceMapDom): """SpecialUniversalCodex_2_0 is codex hard (stable) part of all V2 special universal counter codes that may have optional genus-version override as first code in enclosed group. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ GenericGroup: str = '-A' # Generic Group (Universal with Override). BigGenericGroup: str = '--A' # Big Generic Group (Universal with Override). BodyWithAttachmentGroup: str = '-B' # Message Body plus Attachments Group (Universal with Override). BigBodyWithAttachmentGroup: str = '--B' # Big Message Body plus Attachments Group (Universal with Override). AttachmentGroup: str = '-C' # Message Attachments Only Group (Universal with Override). BigAttachmentGroup: str = '--C' # Big Attachments Only Group (Universal with Override). def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
SUDex_2_0 = SpecialUniversalCodex_2_0()
[docs] @dataclass(frozen=True) class MessageUniversalCodex_2_0(IceMapDom): """MessageUniversalCodex_2_0 is codex hard (stable) part of all V2 message universal counter codes that support CESR native messages. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ DatagramSegmentGroup: str = '-D' # Datagram Segment Group (Universal). BigDatagramSegmentGroup: str = '--D' # Big Datagram Segment Group (Universal). ESSRWrapperGroup: str = '-E' # ESSR Wrapper Group (Universal). BigESSRWrapperGroup: str = '--E' # Big ESSR Wrapper Group (Universal). FixBodyGroup: str = '-F' # Fixed Field Message Body Group (Universal). BigFixBodyGroup: str = '--F' # Big Fixed Field Message Body Group (Universal). MapBodyGroup: str = '-G' # Field Map Message Body Group (Universal). BigMapBodyGroup: str = '--G' # Big Field Map Message Body Group (Universal). NonNativeBodyGroup: str = '-H' # Message body Non-native enclosed with Texter BigNonNativeBodyGroup: str = '--H' # Big Message body Non-native enclosed with Texter def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
MUDex_2_0 = MessageUniversalCodex_2_0() # CodeNames is tuple of codes names given by attributes of union of codices CodeNames = tuple(asdict(CtrDex_2_0) | asdict(CtrDex_1_0)) # Codens is namedtuple of CodeNames where its names are the code names # Codens enables using the attributes of the named tuple to specify a code by # name (indirection) so that changes in the code itself do not break the # creation of a counter. Enables specifying a counter by the code name not the # code itself. The code may change between versions but the code name does not. Codenage = namedtuple("Codenage", CodeNames, defaults=CodeNames) Codens = Codenage()
[docs] @dataclass(frozen=True) class SealCodex_2_0(IceMapDom): """ SealCodex_2_0 is codex of seal counter derivation codes. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. As subclass of MapCodex can get codes with item syntax using tag variables. Example: codex[tag] """ DigestSealSingles: str = '-Q' # Digest Seal Single(s), dig of sealed data. BigDigestSealSingles: str = '--Q' # Big Digest Seal Single(s), dig of sealed data. MerkleRootSealSingles: str = '-R' # Merkle Tree Root Digest Seal Single(s), dig of sealed data. BigMerkleRootSealSingles: str = '--R' # Merkle Tree Root Digest Seal Single(s), dig of sealed data. SealSourceCouples: str = '-S' # Seal Source Couple(s), snu+dig of source sealing or sealed event. BigSealSourceCouples: str = '--S' # Seal Source Couple(s), snu+dig of source sealing or sealed event. SealSourceTriples: str = '-T' # Seal Source Triple(s), pre+snu+dig of source sealing or sealed event. BigSealSourceTriples: str = '--T' # Seal Source Triple(s), pre+snu+dig of source sealing or sealed event. SealSourceLastSingles: str = '-U' # Seal Source Couple(s), pre of last source sealing event. BigSealSourceLastSingles: str = '--U' # Big Seal Source Couple(s), pre of last source sealing event. BackerRegistrarSealCouples: str = '-V' # Backer Registrar Seal Couple(s), brid+dig of sealed data. BigBackerRegistrarSealCouples: str = '--V' # Big Backer Registrar Seal Couple(s), brid+dig of sealed data. TypedDigestSealCouples: str = '-W' # Typed Digest Seal Couple(s), type seal +dig of sealed data. BigTypedDigestSealCouples: str = '--W' # Big Typed Digest Seal Couple(s), type seal +dig of sealed data. def __iter__(self): return iter(astuple(self)) # enables value not key inclusion test with "in"
SealDex_2_0 = SealCodex_2_0() # namedtuple for size entries in Counter derivation code tables # 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 # fs is the full size int number of chars in code Cizage = namedtuple("Cizage", "hs ss fs")
[docs] class Counter: """ Counter is fully qualified cryptographic material primitive base class for counter primitives (framing composition grouping count codes). Sub classes are derivation code and key event element context specific. Includes the following attributes and properties: Class Attributes: Codes (dict): nested of codexes keyed by major and minor version Names (dict): nested of map of code names to codes keyed by major and minor version Hards (dict): of hard code sizes keyed by text domain selector Bards (dict): of hard code sizes keyed by binary domain selector Sizes (dict): of size tables keyed by version. Size table is dict of Sizages keyed by hard code Attributes: Properties: version (Versionage): current CESR code table protocol genus version codes (CounterCodex_1_0 | CounterCodex_1_0): version specific codex sizes (dict): version specific sizes table code (str): hard part of derivation code to indicate cypher suite raw (bytes): crypto material only without code pad (int): number of pad chars given raw count (int): count of quadlets/triplets of following framed material (not including code) qb64 (str | bytes | bytearray): in Base64 fully qualified with derivation code + crypto mat qb64b (bytes | bytearray): in Base64 fully qualified with derivation code + crypto mat qb2 (bytes | bytearray): in binary with derivation code + crypto material Hidden: _version (Versionage): value for .version property _codes (CounterCodex_1_0 | CounterCodex_1_0): version specific codex _sizes (dict): version specific sizes table _code (str): value for .code property _raw (bytes): value for .raw property _count (int): value for .count property Versioning: CESR Genus specific code tables have a major and a minor version. For a given major version all minor versions must be backwards compatible. This means that minor version changes to tables are append only. New codes may be added but no existing codes may be changed. This means that a given implementation need only use use the latest minor version of the code table for a given major version when generating or parsing a primitive or group. Assuming the major versions match, when parsing, a primitive, when that primitive was generated with a later minor version than the implementation supports then it will not be recognized and raise an error. But if a primitive was generated with any earlier minor version than the version the implementation supports then the primitive will parse correctly using any later minor version of the code table. Likewise a given protocol stack may have message bodies that carry a major and a minor version. A given CESR Genus and a given Protocol message stack may be paired in order to synchronize versioning between the two when the message bodies use primitives and or groups defined by codes in the CESR Genus table. In this case pairing is between the CESR Genus labeled KERI_ACDC_SPAC and the message body protocol stack labeled KERI/ACDC/SPAC The two versions, CESR Genus and Protocol Stack, may be synchronized in the following way: * Major versions must match or be compatible * Minor versions may differ but must be compatible within a major version. Importantly the CESR code table version may not be included in the message body itself but only provided in the surrounding CESR stream. This means the code table version used by a message body may not be signed. Therefore the receiver of a message body with embedded CESR primitives and groups must be protected from a CESR code table genus version malleability attack. When the major versions of the CESR code table and protocol stack match, the signed embedded protocol stack major version protects the receiver from a major version malleability attack on the CESR code table. Otherwise the major versions must be compatible in a way that does not allow malleability. For example the set of allowed codes for a given message protocol version are compatible across CESR code table major versions. This, however, does not protect the receiver of a message body from a minor version malleability attack on the CESR code table. Nevertheless, the requirement that all minor versions of a CESR code table for a given major version must be backwards compatible, does indeed provide this protection. Either, the receiver of the message body recognizes exactly all primitives and groups in the message body because the CESR code table minor version supported by the receiver is greater than or equal to that used by the the minor version of the sender or any unsupported (later appended) primitives or group codes will be unrecognized by the received thereby raising an error that results in the message being dropped. """ Codes = \ { Vrsn_1_0.major: \ { Vrsn_1_0.minor: CtrDex_1_0, }, Vrsn_2_0.major: \ { Vrsn_2_0.minor: CtrDex_2_0, }, } # special universal codes SUCodes = \ { Vrsn_1_0.major: \ { Vrsn_1_0.minor: SUDex_1_0, }, Vrsn_2_0.major: \ { Vrsn_2_0.minor: SUDex_2_0, }, } # special universal codes MUCodes = \ { Vrsn_1_0.major: \ { Vrsn_1_0.minor: MUDex_1_0, }, Vrsn_2_0.major: \ { Vrsn_2_0.minor: MUDex_2_0, }, } # invert dataclass codenames: codes to dict codes: codenames Names = copy.deepcopy(Codes) # make deep nested copy so can invert nested values for minor in Names.values(): for key in minor: minor[key] = {val: key for key, val in asdict(minor[key]).items()} # Hards table maps from bytes Base64 first two code chars to int of # hard size, hs,(stable) of code. The soft size, ss, (unstable) for Counter # is always > 0 and hs + ss = fs always Hards = ({('-' + chr(c)): 2 for c in range(65, 65 + 26)}) Hards.update({('-' + chr(c)): 2 for c in range(97, 97 + 26)}) Hards.update([('--', 3)]) Hards.update([('-_', 5)]) # Bards table maps to hard size, hs, of code from bytes holding sextets # converted from first two code char. Used for ._bexfil. Bards = ({codeB64ToB2(c): hs for c, hs in Hards.items()}) # Sizes table indexes size tables first by major version and then by # lastest minor version # Each size table maps hs chars of code to Cizage namedtuple of (hs, ss, fs) # where hs is hard size, ss is soft size, and fs is full size # soft size, ss, should always be > 0 and hs+ss=fs for Counter Sizes = \ { Vrsn_1_0.major: \ { Vrsn_1_0.minor: \ { '-A': Cizage(hs=2, ss=2, fs=4), '-B': Cizage(hs=2, ss=2, fs=4), '-C': Cizage(hs=2, ss=2, fs=4), '-D': Cizage(hs=2, ss=2, fs=4), '-E': Cizage(hs=2, ss=2, fs=4), '-F': Cizage(hs=2, ss=2, fs=4), '-G': Cizage(hs=2, ss=2, fs=4), '-H': Cizage(hs=2, ss=2, fs=4), '-I': Cizage(hs=2, ss=2, fs=4), '-L': Cizage(hs=2, ss=2, fs=4), '--L': Cizage(hs=3, ss=5, fs=8), '-T': Cizage(hs=2, ss=2, fs=4), '--T': Cizage(hs=3, ss=5, fs=8), '-U': Cizage(hs=2, ss=2, fs=4), '--U': Cizage(hs=3, ss=5, fs=8), '-V': Cizage(hs=2, ss=2, fs=4), '--V': Cizage(hs=3, ss=5, fs=8), '-W': Cizage(hs=2, ss=2, fs=4), '--W': Cizage(hs=3, ss=5, fs=8), '-Z': Cizage(hs=2, ss=2, fs=4), '--Z': Cizage(hs=3, ss=5, fs=8), '-_AAA': Cizage(hs=5, ss=3, fs=8), }, }, Vrsn_2_0.major: \ { Vrsn_2_0.minor: \ { '-A': Cizage(hs=2, ss=2, fs=4), '--A': Cizage(hs=3, ss=5, fs=8), '-B': Cizage(hs=2, ss=2, fs=4), '--B': Cizage(hs=3, ss=5, fs=8), '-C': Cizage(hs=2, ss=2, fs=4), '--C': Cizage(hs=3, ss=5, fs=8), '-D': Cizage(hs=2, ss=2, fs=4), '--D': Cizage(hs=3, ss=5, fs=8), '-E': Cizage(hs=2, ss=2, fs=4), '--E': Cizage(hs=3, ss=5, fs=8), '-F': Cizage(hs=2, ss=2, fs=4), '--F': Cizage(hs=3, ss=5, fs=8), '-G': Cizage(hs=2, ss=2, fs=4), '--G': Cizage(hs=3, ss=5, fs=8,), '-H': Cizage(hs=2, ss=2, fs=4), '--H': Cizage(hs=3, ss=5, fs=8), '-I': Cizage(hs=2, ss=2, fs=4), '--I': Cizage(hs=3, ss=5, fs=8), '-J': Cizage(hs=2, ss=2, fs=4,), '--J': Cizage(hs=3, ss=5, fs=8), '-K': Cizage(hs=2, ss=2, fs=4), '--K': Cizage(hs=3, ss=5, fs=8), '-L': Cizage(hs=2, ss=2, fs=4), '--L': Cizage(hs=3, ss=5, fs=8), '-M': Cizage(hs=2, ss=2, fs=4), '--M': Cizage(hs=3, ss=5, fs=8), '-N': Cizage(hs=2, ss=2, fs=4), '--N': Cizage(hs=3, ss=5, fs=8), '-O': Cizage(hs=2, ss=2, fs=4), '--O': Cizage(hs=3, ss=5, fs=8), '-P': Cizage(hs=2, ss=2, fs=4), '--P': Cizage(hs=3, ss=5, fs=8), '-Q': Cizage(hs=2, ss=2, fs=4), '--Q': Cizage(hs=3, ss=5, fs=8), '-R': Cizage(hs=2, ss=2, fs=4), '--R': Cizage(hs=3, ss=5, fs=8), '-S': Cizage(hs=2, ss=2, fs=4), '--S': Cizage(hs=3, ss=5, fs=8), '-T': Cizage(hs=2, ss=2, fs=4), '--T': Cizage(hs=3, ss=5, fs=8), '-U': Cizage(hs=2, ss=2, fs=4), '--U': Cizage(hs=3, ss=5, fs=8), '-V': Cizage(hs=2, ss=2, fs=4), '--V': Cizage(hs=3, ss=5, fs=8), '-W': Cizage(hs=2, ss=2, fs=4), '--W': Cizage(hs=3, ss=5, fs=8), '-X': Cizage(hs=2, ss=2, fs=4), '--X': Cizage(hs=3, ss=5, fs=8), '-Y': Cizage(hs=2, ss=2, fs=4), '--Y': Cizage(hs=3, ss=5, fs=8), '-Z': Cizage(hs=2, ss=2, fs=4), '--Z': Cizage(hs=3, ss=5, fs=8), '-a': Cizage(hs=2, ss=2, fs=4), '--a': Cizage(hs=3, ss=5, fs=8), '-b': Cizage(hs=2, ss=2, fs=4), '--b': Cizage(hs=3, ss=5, fs=8), '-c': Cizage(hs=2, ss=2, fs=4), '--c': Cizage(hs=3, ss=5, fs=8), '-_AAA': Cizage(hs=5, ss=3, fs=8), }, }, }
[docs] @classmethod def makeGVC(cls, version): """Makes genus version code from Versionage version Parameters:: version (Versionage): version portion of Genus Version Code Returns:: qb64b (bytes): qb64b serialized genus version counter for KERI/ACDC genus """ return cls(countB64=cls.verToB64(major=version.major, minor=version.minor), code=Codens.KERIACDCGenusVersion, version=version).qb64b
[docs] @classmethod def enclose(cls, *, qb64=None, qb2=None, code=Codens.AttachmentGroup, version=Vrsn_2_0): """Encloses (frames) CESR stream in qb64 (as bytes) or qb2 (as bytes) with prepended counter of type code. Assumes counter in quadlets/triplets. In V2 CESR, will work with all counters which must count quadlets/triplets) In V1 CESR, will only work with counters that count quadlets/triplets Returns: enclosure (bytearray): stream in qb64 or qb2 with prepended counter of type code. If both qb64 and qb2 are None then empty counter. If qb64 then returns enclosure as bytearray in qb64 text domain If qb2 then returns enclosure as bytearray in qb2 binary domain Parameters: qb64 (str|bytes|bytearray|memoryview|None): qualified Base64 sub-stream to be enclosed. May be empty. None means use qb2 if provided. qb2 (bytes|bytearray|memoryview|None): qualified Base2 sub-stream to be enclosed. May be empty. None means ignore code (str): either stable (hard) part of derivation code or code name. When code name then look up code from ._codes. This allows versioning to change code but keep stable code name. """ if qb64 is None and qb2 is None: qb64 = b'' # default counter of empty content enclosure = bytearray() if qb64 is not None: # process qb64 in text domain if hasattr(qb64, "encode"): qb64 = qb64.encode() # convert to bytes if isinstance(qb64, memoryview): qb64 = bytearray(qb64) # converts memoryview to bytearray length = len(qb64) if length % 4: # invalid sized qb64 not aligned on 24 bit boundaries raise ValueError(f"Bad enclosed qb64 {length=}") count = length // 4 # processes code as codens code name counter = cls(code=code, count=count, version=version) if version.major < Vrsn_2_0.major and counter.code not in (QTDex_1_0): raise ValueError(f"Non V1 quadlet/triplet counter code={counter.code}") enclosure.extend(counter.qb64b) enclosure.extend(qb64) else: # process qb2 in binary domain if isinstance(qb2, memoryview): qb2 = bytearray(qb2) # converts memoryview to bytearray length = len(qb2) if length % 3: # invalid sized qb64 not aligned on 24 bit boundaries raise ValueError(f"Bad enclosed qb2 {length=}") count = length // 3 # processes code as codens code name counter = cls(code=code, count=count, version=version) if version.major < Vrsn_2_0.major and counter.code not in (QTDex_1_0): raise ValueError(f"Non V1 quadlet/triplet counter code={counter.code}") enclosure.extend(counter.qb2) enclosure.extend(qb2) return enclosure
[docs] def __init__(self, code=None, *, count=None, countB64=None, qb64b=None, qb64=None, qb2=None, strip=False, version=Vrsn_2_0, **kwa): """Validate as fully qualified Parameters: code (str | None): either stable (hard) part of derivation code or code name. When code name then look up code from ._codes. This allows versioning to change code but keep stable code name. count (int | None): count of framed material in quadlets/triplets for composition. Count does not include code. When both count and countB64 are None then count defaults to 1 countB64 (str | None): count of framed material in quadlets/triplets for composition as Base64 representation of int. useful for genus-version version as count qb64b (bytes | bytearray | None): fully qualified crypto material text domain if code nor tag is provided qb64 (str | None) fully qualified crypto material text domain if code nor tag not qb64b is provided qb2 (bytes | bytearray | None) fully qualified crypto material binary domain if code nor tag not qb64b nor qb54 is provided strip (bool): True means strip counter contents from input stream bytearray after parsing qb64b or qb2. False means do not strip. default False version (Versionage): instance of genera version of CESR code tables Needs either code or qb64b or qb64 or qb2 Otherwise raises EmptyMaterialError When code and count provided then validate that code and count are correct Else when qb64b or qb64 or qb2 provided extract and assign .code and .count """ if version.major not in self.Sizes: raise InvalidVersionError(f"Unsupported major version=" f"{version.major}.") latest = list(self.Sizes[version.major])[-1] # get latest supported minor version if version.minor > latest: raise InvalidVersionError(f"Minor version={version.minor} " f" exceeds latest supported minor" f" version={latest}.") self._codes = self.Codes[version.major][latest] # use latest supported version codes self._sizes = self.Sizes[version.major][latest] # use latest supported version sizes self._version = version # provided version may be earlier than supported version if code: # code (hard) provided # assumes ._sizes ._codes coherent if code not in self._sizes or len(code) < 2: try: code = self._codes[code] # code is code name so look up code if code not in self._sizes or len(code) < 2: raise InvalidCodeError(f"Unsupported {code=}.") except Exception as ex: raise InvalidCodeError(f"Unsupported {code=}.") from ex hs, ss, fs = self._sizes[code] # get sizes for code cs = hs + ss # both hard + soft code size if hs < 2 or fs != cs or cs % 4: # fs must be bs and multiple of 4 for count codes raise InvalidCodeSizeError(f"Whole code size not full " f"size or not multiple of 4. " f"{cs=} {fs=}.") if count is None: count = 1 if countB64 is None else b64ToInt(countB64) if code[1] not in ("0123456789_"): # small or opcode [A-Z,a-z] or large [-] if ss not in (2, 5): # not valid dynamic soft sizes raise InvalidVarIndexError(f"Invalid {ss=} " f"for {code=}.") # dynamically promote code based on count if code[1] != '-' and count > (64 ** 2 - 1): # small code but large count # elevate code due to large count code = f"--{code[1]}" # promote hard ss = 5 if count < 0 or count > (64 ** ss - 1): raise InvalidVarIndexError(f"Invalid {count=} for " f"{code=} with {ss=}.") self._code = code self._count = count elif qb64b is not None: self._exfil(qb64b) if strip: # assumes bytearray del qb64b[:self._sizes[self.code].fs] elif qb64 is not None: self._exfil(qb64) elif qb2 is not None: # rewrite to use direct binary exfiltration self._bexfil(qb2) if strip: # assumes bytearray del qb2[:self._sizes[self.code].fs * 3 // 4] else: raise EmptyMaterialError("Improper initialization need either " "(code and count) or qb64b or " "qb64 or qb2.") self._name = self.Names[version.major][latest][self.code]
@property def version(self): """Makes .version read only Returns ._version genusversion """ return self._version @property def codes(self): """Makes .codes read only Returns ._codes """ return self._codes @property def sizes(self): """Makes .sizes read only Returns ._sizes """ return self._sizes @property def code(self): """Property for code Returns: code (str): hard part only of full text code. Getter for ._code. Makes .code read only Soft part is count """ return self._code @property def name(self): """Getter for ._name. Makes .name read only Returns: name (str): code name for self.code. Match interface for annotation for primitives like Matter """ return self._name @property def hard(self): """Alias for .code Returns: hard (str): hard part only of full text code. Alias for .code. """ return self.code @property def count(self): """Getter for ._count. Makes ._count read only Returns: count (int): count value in quadlets/triples chars/bytes of material framed by counter. """ return self._count @property def soft(self): """Converts .count to b64 Returns: soft (str): Base64 soft part of full counter code. Count value in quadlets/triples chars/bytes of material framed by counter. """ _, ss, _ = self.sizes[self.code] return intToB64(self._count, l=ss) @property def both(self): """Getter for combined hard + soft parts of full text code Returns: both (str): hard + soft parts of full text code """ return f"{self.hard}{self.soft}" @property def fullSize(self): """Getter for full size of counter in bytes in text domain Returns" fs (int): full size of counter in bytes in text domain """ _, _, fs = self.sizes[self.code] # get from sizes table return fs @property def qb64b(self): """Property qb64b: Returns: Fully Qualified Base64 Version encoded as bytes Assumes self.raw and self.code are correctly populated """ return self._infil() @property def qb64(self): """Property qb64: Returns: Fully Qualified Base64 Version, same as .both Assumes self.raw and self.code are correctly populated """ return self.qb64b.decode("utf-8") @property def qb2(self): """Property qb2: Returns Fully Qualified Binary Version Bytes """ return self._binfil()
[docs] def countToB64(self, l=None): """ Returns count as Base64 left padded with "A"s Parameters: l (int | None): minimum number characters including left padding When not provided use the softsize of .code """ if l is None: _, ss, _ = self._sizes[self.code] l = ss return (intToB64(self.count, l=l))
[docs] def byteCount(self, cold=Colds.txt): """Computes number of bytes from .count quadlets/triplets given cold Returns: byteCount (int): number of bytes in .count quadlets/triplets given cold Parameters: cold (str): value of Coldage to indicate if text (qb64) or binary (qb2) in order to convert .count quadlets/triplets to byte count if not Colds.txt or Colds.bny raises ValueError """ if cold == Colds.txt: # quadlets return self.count * 4 if cold == Colds.bny: # triplets return self.count * 3 raise ValueError(f"Invalid {cold=} for byte count conversion")
[docs] def byteSize(self, cold=Colds.txt): """Computes number of bytes from .fullSize given cold (text or binary domain) so can strip appropriate number of bytes from stream when peeking at counter in stream without stripping it. Returns: byteSize (int): number of bytes given cold (text or binary domain) Parameters: cold (str): value of Coldage to indicate if text (qb64) or binary (qb2) in order to convert .count quadlets/triplets to byte count if not Colds.txt or Colds.bny raises ValueError """ if cold == Colds.txt: return self.fullSize if cold == Colds.bny: return (self.fullSize // 4) * 3 raise ValueError(f"Invalid {cold=} for byte size conversion")
[docs] @staticmethod def verToB64(version=None, *, text="", major=0, minor=0): """ Converts version to Base64 representation of countB64 suitable for CESR protocol genus and version Returns: countB64 (str): suitable for input to Counter param countB64 for creating genus-version code version count portion Parameters: version (Versionage): instange of namedtuple Versionage(major=major,minor=minor) text (str): text format of version as dotted decimal "major.minor" major (int): When version is None and verstr is empty then use major minor range [0, 63] for one Base64 character minor (int): When version is None and verstr is empty then use major minor range [0, 4095] for two Base64 characters Example: Counter(countB64=Counter.verToB64(text = "1.0")) Counter(countB64=Counter.verToB64(major=1, minor=0)) """ if version: major = version.major minor = version.minor elif text: splits = text.split(".", maxsplit=2) splits = [(int(s) if s else 0) for s in splits] parts = [major, minor] for i in range(2-len(splits),0, -1): # append missing minor and/or major splits.append(parts[-i]) major = splits[0] minor = splits[1] if major < 0 or major > 63 or minor < 0 or minor > 4095: raise ValueError(f"Out of bounds version = {major}.{minor}.") return (f"{intToB64(major)}{intToB64(minor, l=2)}")
[docs] @staticmethod def b64ToVer(b64, *, texted=False): """ Converts Base64 representation of version to Versionage or text dotted decimal format default is Versionage Returns: version (Versionage | str): Example: Counter(version=Counter.b64ToVer("BAA")) Parameters: b64 (str): base64 string of three characters Mmm for Major minor texted (bool): return text format dotted decimal string """ if not Reb64.match(b64.encode("utf-8")): raise ValueError("Invalid Base64.") if texted: return ".".join([f"{b64ToInt(b64[0])}", f"{b64ToInt(b64[1:3])}"]) return Versionage(major=b64ToInt(b64[0]), minor=b64ToInt(b64[1:3]))
def _infil(self): """Returns fully qualified attached sig base64 bytes computed from self.code and self.count. """ code = self.code # codex value chars hard code count = self.count # index value int used for soft hs, ss, fs = self._sizes[code] # assumes fs = hs + ss # both hard + soft size # assumes unit tests ensure ._sizes table entries are consistent # hs >= 2, ss > 0 fs == hs + ss, not (fs % 4) if count < 0 or count > (64 ** ss - 1): raise InvalidVarIndexError("Invalid count={} for code={}.".format(count, code)) # both is hard code + converted count both = "{}{}".format(code, intToB64(count, l=ss)) # check valid pad size for whole code size if len(both) % 4: # no pad raise InvalidCodeSizeError("Invalid size = {} of {} not a multiple of 4." .format(len(both), both)) # prepending full derivation code with index and strip off trailing pad characters return (both.encode("utf-8")) def _binfil(self): """Returns bytes of fully qualified base2 bytes, that is .qb2 self.code converted to Base2 left shifted with pad bits equivalent of Base64 decode of .qb64 into .qb2 """ code = self.code # codex chars hard code count = self.count # index value int used for soft hs, ss, fs = self._sizes[code] # assumes fs = hs + ss # assumes unit tests ensure ._sizes table entries are consistent # hs >= 2, ss>0 fs == hs + ss, not (fs % 4) if count < 0 or count > (64 ** ss - 1): raise InvalidVarIndexError("Invalid count={} for code={}.".format(count, code)) # both is hard code + converted count both = "{}{}".format(code, intToB64(count, l=ss)) if len(both) != fs: raise InvalidCodeSizeError("Mismatch code size = {} with table = {}." .format(fs, len(both))) return (codeB64ToB2(both)) # convert to b2 left shift if any def _exfil(self, qb64b): """Extracts self.code and self.count from qualified base64 bytes qb64b """ if not qb64b or len(qb64b) < 2: # need more bytes raise ShortageError("Empty material, Need more characters.") first = qb64b[:2] # extract first two char code selector if hasattr(first, "decode"): first = first.decode("utf-8") if first not in self.Hards: if first[0] == '_': raise UnexpectedOpCodeError("Unexpected op code start" "while extracing Counter.") else: raise UnexpectedCodeError("Unsupported code start ={}.".format(first)) hs = self.Hards[first] # get hard code size if len(qb64b) < hs: # need more bytes raise ShortageError("Need {} more characters.".format(hs - len(qb64b))) hard = qb64b[:hs] # get hard code if hasattr(hard, "decode"): hard = hard.decode("utf-8") # decode converts bytearray/bytes to str if hard not in self._sizes: # Sizes needs str not bytes raise UnexpectedCodeError("Unsupported code ={}.".format(hard)) hs, ss, fs = self._sizes[hard] # assumes hs consistent in both tables # assumes fs = hs + ss # both hard + soft code size # assumes that unit tests on Counter and CounterCodex ensure that # .Codes and .Sizes are well formed. # hs consistent and hs >= 2 and ss > 0 and fs = hs + ss and not fs % 4 if len(qb64b) < fs: # need more bytes raise ShortageError("Need {} more characters.".format(fs - len(qb64b))) count = qb64b[hs:fs] # extract count chars if hasattr(count, "decode"): count = count.decode("utf-8") count = b64ToInt(count) # compute int count self._code = hard self._count = count def _bexfil(self, qb2): """Extracts self.code and self.count from qualified base2 bytes qb2 """ if not qb2 or len(qb2) < 2: # need more bytes raise ShortageError("Empty material, Need more bytes.") first = nabSextets(qb2, 2) # extract first two sextets as code selector if first not in self.Bards: if first[0] == b'\xfc': # b64ToB2('_') raise UnexpectedOpCodeError("Unexpected op code start" "while extracing Matter.") else: raise UnexpectedCodeError("Unsupported code start sextet={}.".format(first)) hs = self.Bards[first] # get code hard size equvalent sextets bhs = sceil(hs * 3 / 4) # bhs is min bytes to hold hs sextets if len(qb2) < bhs: # need more bytes raise ShortageError("Need {} more bytes.".format(bhs - len(qb2))) hard = codeB2ToB64(qb2, hs) # extract and convert hard part of code if hard not in self._sizes: raise UnexpectedCodeError("Unsupported code ={}.".format(hard)) hs, ss, fs = self._sizes[hard] # assumes fs = hs + ss # both hs and ss # assumes that unit tests on Counter and CounterCodex ensure that # .Codes and .Sizes are well formed. # hs consistent and hs >= 2 and ss > 0 and fs = hs + ss and not fs % 4 bcs = sceil(fs * 3 / 4) # bcs is min bytes to hold fs sextets if len(qb2) < bcs: # need more bytes raise ShortageError("Need {} more bytes.".format(bcs - len(qb2))) both = codeB2ToB64(qb2, fs) # extract and convert both hard and soft part of code count = b64ToInt(both[hs:fs]) # get count self._code = hard self._count = count