Source code for keri.core.indexing

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

Provides versioning support for Indexer classes and codes
"""
from collections import namedtuple
from dataclasses import dataclass, astuple, asdict
from base64 import urlsafe_b64encode as encodeB64
from base64 import urlsafe_b64decode as decodeB64

from ..kering import (EmptyMaterialError, RawMaterialError, InvalidCodeSizeError,
                      InvalidVarIndexError, ConversionError, ValidationError,
                      ShortageError, UnexpectedCodeError,
                      UnexpectedCountCodeError, UnexpectedOpCodeError)

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


[docs] @dataclass(frozen=True) class IndexerCodex: """ IndexerCodex is codex hard (stable) part of all indexer derivation codes. Codes indicate which list of keys, current and/or prior next, index is for: _Sig: Indices in code may appear in both current signing and prior next key lists when event has both current and prior next key lists. Two character code table has only one index so must be the same for both lists. Other index if for prior next. The indices may be different in those code tables which have two sets of indices. _Crt_Sig: Index in code for current signing key list only. _Big_: Big index values Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ Ed25519_Sig: str = 'A' # Ed25519 sig appears same in both lists if any. Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only. ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any. ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list. ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any. ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list. Ed448_Sig: str = '0A' # Ed448 signature appears in both lists. Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only. Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both lists. Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only. ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists. ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists. ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only. Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists. Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only. TBD0: str = '0z' # Test of Var len label L=N*4 <= 4095 char quadlets includes code TBD1: str = '1z' # Test of index sig lead 1 TBD4: str = '4z' # Test of index sig lead 1 big def __iter__(self): return iter(astuple(self)) # enables inclusion test with "in"
IdrDex = IndexerCodex()
[docs] @dataclass(frozen=True) class IndexedSigCodex: """IndexedSigCodex is codex all indexed signature derivation codes. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ Ed25519_Sig: str = 'A' # Ed25519 sig appears same in both lists if any. Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only. ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any. ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list. ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any. ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list. Ed448_Sig: str = '0A' # Ed448 signature appears in both lists. Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only. Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both lists. Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only. ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists. ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists. ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only. Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists. Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only. def __iter__(self): return iter(astuple(self))
IdxSigDex = IndexedSigCodex() # Make instance
[docs] @dataclass(frozen=True) class IndexedCurrentSigCodex: """IndexedCurrentSigCodex is codex indexed signature codes for current list. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ Ed25519_Crt_Sig: str = 'B' # Ed25519 sig appears in current list only. ECDSA_256k1_Crt_Sig: str = 'D' # ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Crt_Sig: str = "F" # ECDSA secp256r1 sig appears in current list. Ed448_Crt_Sig: str = '0B' # Ed448 signature appears in current list only. Ed25519_Big_Crt_Sig: str = '2B' # Ed25519 sig appears in current list only. ECDSA_256k1_Big_Crt_Sig: str = '2D' # ECDSA secp256k1 sig appears in current list only. ECDSA_256r1_Big_Crt_Sig: str = "2F" # ECDSA secp256r1 sig appears in current list only. Ed448_Big_Crt_Sig: str = '3B' # Ed448 signature appears in current list only. def __iter__(self): return iter(astuple(self))
IdxCrtSigDex = IndexedCurrentSigCodex() # Make instance
[docs] @dataclass(frozen=True) class IndexedBothSigCodex: """IndexedBothSigCodex is codex indexed signature codes for both lists. Only provide defined codes. Undefined are left out so that inclusion(exclusion) via 'in' operator works. """ Ed25519_Sig: str = 'A' # Ed25519 sig appears same in both lists if any. ECDSA_256k1_Sig: str = 'C' # ECDSA secp256k1 sig appears same in both lists if any. ECDSA_256r1_Sig: str = "E" # ECDSA secp256r1 sig appears same in both lists if any. Ed448_Sig: str = '0A' # Ed448 signature appears in both lists. Ed25519_Big_Sig: str = '2A' # Ed25519 sig appears in both listsy. ECDSA_256k1_Big_Sig: str = '2C' # ECDSA secp256k1 sig appears in both lists. ECDSA_256r1_Big_Sig: str = "2E" # ECDSA secp256r1 sig appears in both lists. Ed448_Big_Sig: str = '3A' # Ed448 signature appears in both lists. def __iter__(self): return iter(astuple(self))
IdxBthSigDex = IndexedBothSigCodex() # Make instance # namedtuple for size entries in Incexer 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 # os is the other size int number of chars in other index part of soft # 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")
[docs] class Indexer: """ Indexer is fully qualified cryptographic material primitive base class for indexed primitives. In special cases some codes in the Index code table may be of variable length (i.e. not indexed) when the full size table entry is None. In that case the index is used instread as the length. Sub classes are derivation code and key event element context specific. Includes the following attributes and properties: Attributes: Properties: code is str of stable (hard) part of derivation code raw (bytes): unqualified crypto material usable for crypto operations index (int): main index offset into list or length of material ondex (int | None): other index offset into list or length of material qb64b (bytes): fully qualified Base64 crypto material qb64 (str | bytes): fully qualified Base64 crypto material qb2 (bytes): fully qualified binary crypto material Hidden: ._code (str): value for .code property ._raw (bytes): value for .raw property ._index (int): value for .index property ._ondex (int): value for .ondex property ._infil is method to compute fully qualified Base64 from .raw and .code ._binfil is method to compute fully qualified Base2 from .raw and .code ._exfil is method to extract .code and .raw from fully qualified Base64 ._bexfil is method to extract .code and .raw from fully qualified Base2 """ # Hards table maps from bytes Base64 first code char to int of hard size, hs, # (stable) of code. The soft size, ss, (unstable) is always > 0 for Indexer. Hards = ({chr(c): 1 for c in range(65, 65 + 26)}) Hards.update({chr(c): 1 for c in range(97, 97 + 26)}) Hards.update([('0', 2), ('1', 2), ('2', 2), ('3', 2), ('4', 2)]) # Sizes table maps hs chars of code to Xizage namedtuple of (hs, ss, os, fs, ls) # where hs is hard size, ss is soft size, os is other index size, # and fs is full size, ls is lead size. # where ss includes os, so main index size ms = ss - os # soft size, ss, should always be > 0 for Indexer Sizes = { 'A': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'B': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'C': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'D': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'E': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), 'F': Xizage(hs=1, ss=1, os=0, fs=88, ls=0), '0A': Xizage(hs=2, ss=2, os=1, fs=156, ls=0), '0B': Xizage(hs=2, ss=2, os=1, fs=156, ls=0), '2A': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2B': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2C': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2D': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2E': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '2F': Xizage(hs=2, ss=4, os=2, fs=92, ls=0), '3A': Xizage(hs=2, ss=6, os=3, fs=160, ls=0), '3B': Xizage(hs=2, ss=6, os=3, fs=160, ls=0), '0z': Xizage(hs=2, ss=2, os=0, fs=None, ls=0), '1z': Xizage(hs=2, ss=2, os=1, fs=76, ls=1), '4z': Xizage(hs=2, ss=6, os=3, fs=80, ls=1), } # Bards table maps to hard size, hs, of code from bytes holding sextets # converted from first code char. Used for ._bexfil. Bards = ({codeB64ToB2(c): hs for c, hs in Hards.items()}) Codes = asdict(IdrDex) # map code name to code Names = {val : key for key, val in Codes.items()} # invert map code to code name @classmethod def _rawSize(cls, code): """ Returns expected raw size in bytes for a given code. Not applicable to codes with fs = None """ hs, ss, os, fs, ls = cls.Sizes[code] # get sizes return ((fs - (hs + ss)) * 3 // 4)
[docs] def __init__(self, raw=None, code=IdrDex.Ed25519_Sig, index=0, ondex=None, qb64b=None, qb64=None, qb2=None, strip=False, **kwa): """ Validate as fully qualified. Parameters: raw (bytes): unqualified crypto material usable for crypto operations code (str): stable (hard) part of derivation code index (int): main index offset into list or length of material ondex (int | None): other index offset into list or length of material qb64b (bytes): fully qualified Base64 crypto material qb64 (str | bytes): fully qualified Base64 crypto material qb2 (bytes): fully qualified binary crypto material strip (bool): True means strip counter contents from input stream bytearray after parsing qb64b or qb2. False means do not strip Notes: Needs either (raw and code and index) or qb64b or qb64 or qb2. Otherwise raises EmptyMaterialError. When raw and code provided then validate that code is correct for length of raw and assign .raw. Else when qb64b or qb64 or qb2 provided extract and assign .raw, .code, .index, .ondex. """ if raw is not None: # raw provided if not code: raise EmptyMaterialError("Improper initialization need either " "(raw and code) or qb64b or qb64 or qb2.") if not isinstance(raw, (bytes, bytearray)): raise TypeError(f"Not a bytes or bytearray, raw={raw}.") if code not in self.Sizes: raise UnexpectedCodeError(f"Unsupported code={code}.") hs, ss, os, fs, ls = self.Sizes[code] # get sizes for code cs = hs + ss # both hard + soft code size ms = ss - os if not isinstance(index, int) or index < 0 or index > (64 ** ms - 1): raise InvalidVarIndexError(f"Invalid index={index} for code={code}.") if isinstance(ondex, int) and os and not (ondex >= 0 and ondex <= (64 ** os - 1)): raise InvalidVarIndexError(f"Invalid ondex={ondex} for code={code}.") if code in IdxCrtSigDex and ondex is not None: raise InvalidVarIndexError(f"Non None ondex={ondex} for code={code}.") if code in IdxBthSigDex: if ondex is None: # set default ondex = index # when not provided make ondex match index else: if ondex != index and os == 0: # must match if os == 0 raise InvalidVarIndexError(f"Non matching ondex={ondex}" f" and index={index} for " f"code={code}.") if not fs: # compute fs from index if cs % 4: raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for " f"variable length material. cs={cs}.") if os != 0: raise InvalidCodeSizeError(f"Non-zero other index size for " f"variable length material. os={os}.") fs = (index * 4) + cs rawsize = (fs - cs) * 3 // 4 raw = raw[:rawsize] # copy rawsize from stream, may be less if len(raw) != rawsize: # forbids shorter raise RawMaterialError(f"Not enougth raw bytes for code={code}" f"and index={index} ,expected {rawsize} " f"got {len(raw)}.") self._code = code self._index = index self._ondex = ondex self._raw = bytes(raw) # crypto ops require bytes not bytearray elif qb64b is not None: self._exfil(qb64b) if strip: # assumes bytearray del qb64b[:len(self.qb64b)] # may be variable length fs elif qb64 is not None: self._exfil(qb64) elif qb2 is not None: self._bexfil(qb2) if strip: # assumes bytearray del qb2[:len(self.qb2)] # may be variable length fs else: raise EmptyMaterialError("Improper initialization need either " "(raw and code and index) or qb64b or " "qb64 or qb2.")
@property def code(self): """ Returns ._code Makes .code read only """ return self._code @property def name(self): """ Returns: name (str): code name for self.code. Used for annotation for primitives like Matter """ return self.Names[self.code] @property def raw(self): """ Returns ._raw Makes .raw read only """ return self._raw @property def index(self): """ Returns ._index Makes .index read only """ return self._index @property def ondex(self): """ Returns ._ondex Makes .ondex read only """ return self._ondex @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 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() def _infil(self): """ Returns fully qualified attached sig base64 bytes computed from self.raw, self.code and self.index. cs = hs + ss os = ss - ms (main index size) when fs None then size computed & fs = size * 4 + cs """ code = self.code # codex value chars hard code index = self.index # main index value ondex = self.ondex # other index value raw = self.raw # bytes or bytearray ps = (3 - (len(raw) % 3)) % 3 # if lead then same pad size chars & lead size bytes hs, ss, os, fs, ls = self.Sizes[code] cs = hs + ss ms = ss - os if not fs: # compute fs from index if cs % 4: raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for " f"variable length material. cs={cs}.") if os != 0: raise InvalidCodeSizeError(f"Non-zero other index size for " f"variable length material. os={os}.") fs = (index * 4) + cs if index < 0 or index > (64 ** ms - 1): raise InvalidVarIndexError(f"Invalid index={index} for code={code}.") if (isinstance(ondex, int) and os and not (ondex >= 0 and ondex <= (64 ** os - 1))): raise InvalidVarIndexError(f"Invalid ondex={ondex} for os={os} and " f"code={code}.") # both is hard code + converted index + converted ondex both = (f"{code}{intToB64(index, l=ms)}" f"{intToB64(ondex if ondex is not None else 0, l=os)}") # check valid pad size for whole code size, assumes ls is zero if len(both) != cs: raise InvalidCodeSizeError("Mismatch code size = {} with table = {}." .format(cs, len(both))) if (cs % 4) != ps - ls: # adjusted pad given lead bytes raise InvalidCodeSizeError(f"Invalid code={both} for converted" f" raw pad size={ps}.") # prepend pad bytes, convert, then replace pad chars with full derivation # code including index, full = both.encode("utf-8") + encodeB64(bytes([0] * ps) + raw)[ps - ls:] if len(full) != fs: # invalid size raise InvalidCodeSizeError(f"Invalid code={both} for raw size={len(raw)}.") return full def _binfil(self): """ Returns bytes of fully qualified base2 bytes, that is .qb2 self.code and self.index converted to Base2 + self.raw left shifted with pad bits equivalent of Base64 decode of .qb64 into .qb2 """ code = self.code # codex chars hard code index = self.index # main index value ondex = self.ondex # other index value raw = self.raw # bytes or bytearray ps = (3 - (len(raw) % 3)) % 3 # same pad size chars & lead size bytes hs, ss, os, fs, ls = self.Sizes[code] cs = hs + ss ms = ss - os if index < 0 or index > (64 ** ss - 1): raise InvalidVarIndexError(f"Invalid index={index} for code={code}.") if (isinstance(ondex, int) and os and not (ondex >= 0 and ondex <= (64 ** os - 1))): raise InvalidVarIndexError(f"Invalid ondex={ondex} for os={os} and " f"code={code}.") if not fs: # compute fs from index if cs % 4: raise InvalidCodeSizeError(f"Whole code size not multiple of 4 for " f"variable length material. cs={cs}.") if os != 0: raise InvalidCodeSizeError(f"Non-zero other index size for " f"variable length material. os={os}.") fs = (index * 4) + cs # both is hard code + converted index both = (f"{code}{intToB64(index, l=ms)}" f"{intToB64(ondex if ondex is not None else 0, l=os)}") if len(both) != cs: raise InvalidCodeSizeError("Mismatch code size = {} with table = {}." .format(cs, len(both))) if (cs % 4) != ps - ls: # adjusted pad given lead bytes raise InvalidCodeSizeError(f"Invalid code={both} for converted" f" raw pad size={ps}.") n = sceil(cs * 3 / 4) # number of b2 bytes to hold b64 code + index # convert code both to right align b2 int then left shift in pad bits # then convert to bytes bcode = (b64ToInt(both) << (2 * (ps - ls))).to_bytes(n, 'big') full = bcode + bytes([0] * ls) + raw bfs = len(full) # binary full size if bfs % 3 or (bfs * 4 // 3) != fs: # invalid size raise InvalidCodeSizeError(f"Invalid code={both} for raw size={len(raw)}.") return full def _exfil(self, qb64b): """ Extracts self.code, self.index, and self.raw from qualified base64 bytes qb64b cs = hs + ss ms = ss - os (main index size) when fs None then size computed & fs = size * 4 + cs """ if not qb64b: # empty need more bytes raise ShortageError("Empty material.") first = qb64b[:1] # extract first char code selector if hasattr(first, "decode"): first = first.decode("utf-8") if first not in self.Hards: if first[0] == '-': raise UnexpectedCountCodeError("Unexpected count code start" "while extracing Indexer.") elif first[0] == '_': raise UnexpectedOpCodeError("Unexpected op code start" "while extracing Indexer.") else: raise UnexpectedCodeError(f"Unsupported code start char={first}.") hs = self.Hards[first] # get hard code size if len(qb64b) < hs: # need more bytes raise ShortageError(f"Need {hs - len(qb64b)} more characters.") hard = qb64b[:hs] # get hard code if hasattr(hard, "decode"): hard = hard.decode("utf-8") if hard not in self.Sizes: raise UnexpectedCodeError(f"Unsupported code ={hard}.") hs, ss, os, fs, ls = self.Sizes[hard] # assumes hs in both tables consistent cs = hs + ss # both hard + soft code size ms = ss - os # assumes that unit tests on Indexer and IndexerCodex ensure that # .Codes and .Sizes are well formed. # hs consistent and hs > 0 and ss > 0 and (fs >= hs + ss if fs is not None else True) # assumes no variable length indexed codes so fs is not None if len(qb64b) < cs: # need more bytes raise ShortageError(f"Need {cs - len(qb64b)} more characters.") index = qb64b[hs:hs+ms] # extract index/size chars if hasattr(index, "decode"): index = index.decode("utf-8") index = b64ToInt(index) # compute int index ondex = qb64b[hs+ms:hs+ms+os] # extract ondex chars if hasattr(ondex, "decode"): ondex = ondex.decode("utf-8") if hard in IdxCrtSigDex: # if current sig then ondex from code must be 0 ondex = b64ToInt(ondex) if os else None # compute ondex from code if ondex: # not zero or None so error raise ValueError(f"Invalid ondex={ondex} for code={hard}.") else: ondex = None # zero so set to None when current only else: ondex = b64ToInt(ondex) if os else index # index is index for some codes and variable length for others if not fs: # compute fs from index which means variable length if cs % 4: raise ValidationError(f"Whole code size not multiple of 4 for " f"variable length material. cs={cs}.") if os != 0: raise ValidationError(f"Non-zero other index size for " f"variable length material. os={os}.") fs = (index * 4) + cs if len(qb64b) < fs: # need more bytes raise ShortageError(f"Need {fs - len(qb64b)} more chars.") qb64b = qb64b[:fs] # fully qualified primitive code plus material if hasattr(qb64b, "encode"): # only convert extracted chars from stream qb64b = qb64b.encode("utf-8") # strip off prepended code and append pad characters #ps = cs % 4 # pad size ps = cs mod 4, same pad chars and lead bytes #base = ps * b'A' + qb64b[cs:] # replace prepend code with prepad zeros #raw = decodeB64(base)[ps+ls:] # decode and strip off ps+ls prepad bytes # check for non-zeroed pad bits or lead bytes ps = cs % 4 # code pad size ps = cs mod 4 pbs = 2 * (ps if ps else ls) # pad bit size in bits if ps: # ps. IF ps THEN not ls (lead) and vice versa OR not ps and not ls base = ps * b'A' + qb64b[cs:] # replace pre code with prepad chars of zero paw = decodeB64(base) # decode base to leave prepadded raw pi = (int.from_bytes(paw[:ps], "big")) # prepad as int if pi & (2 ** pbs - 1 ): # masked pad bits non-zero raise ValueError(f"Non zeroed prepad bits = " f"{pi & (2 ** pbs - 1 ):<06b} in {qb64b[cs:cs+1]}.") raw = paw[ps:] # strip off ps prepad paw bytes else: # not ps. IF not ps THEN may or may not be ls (lead) base = qb64b[cs:] # strip off code leaving lead chars if any and value # decode lead chars + val leaving lead bytes + raw bytes # then strip off ls lead bytes leaving raw paw = decodeB64(base) # decode base to leave prepadded paw bytes li = int.from_bytes(paw[:ls], "big") # lead as int if li: # pre pad lead bytes must be zero if ls == 1: raise ValueError(f"Non zeroed lead byte = 0x{li:02x}.") else: raise ValueError(f"Non zeroed lead bytes = 0x{li:04x}.") raw = paw[ls:] if len(raw) != (len(qb64b) - cs) * 3 // 4: # exact lengths raise ConversionError(f"Improperly qualified material = {qb64b}") self._code = hard self._index = index self._ondex = ondex self._raw = raw # must be bytes for crpto opts and immutable not bytearray def _bexfil(self, qb2): """ Extracts self.code, self.index, and self.raw from qualified base2 bytes qb2 cs = hs + ss ms = ss - os (main index size) when fs None then size computed & fs = size * 4 + cs """ if not qb2: # empty need more bytes raise ShortageError("Empty material, Need more bytes.") first = nabSextets(qb2, 1) # extract first sextet as code selector if first not in self.Bards: if first[0] == b'\xf8': # b64ToB2('-') raise UnexpectedCountCodeError("Unexpected count code start" "while extracing Matter.") elif first[0] == b'\xfc': # b64ToB2('_') raise UnexpectedOpCodeError("Unexpected op code start" "while extracing Matter.") else: raise UnexpectedCodeError(f"Unsupported code start sextet={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(f"Need {bhs - len(qb2)} more bytes.") hard = codeB2ToB64(qb2, hs) # extract and convert hard part of code if hard not in self.Sizes: raise UnexpectedCodeError(f"Unsupported code ={hard}.") hs, ss, os, fs, ls = self.Sizes[hard] cs = hs + ss # both hs and ss ms = ss - os # assumes that unit tests on Indexer and IndexerCodex ensure that # .Codes and .Sizes are well formed. # hs consistent and hs > 0 and ss > 0 and (fs >= hs + ss if fs is not None else True) bcs = sceil(cs * 3 / 4) # bcs is min bytes to hold cs sextets if len(qb2) < bcs: # need more bytes raise ShortageError("Need {} more bytes.".format(bcs - len(qb2))) both = codeB2ToB64(qb2, cs) # extract and convert both hard and soft part of code index = b64ToInt(both[hs:hs+ms]) # compute index if hard in IdxCrtSigDex: # if current sig then ondex from code must be 0 ondex = b64ToInt(both[hs+ms:hs+ms+os]) if os else None # compute ondex from code if ondex: # not zero or None so error raise ValueError(f"Invalid ondex={ondex} for code={hard}.") else: ondex = None # zero so set to None when current only else: ondex = b64ToInt(both[hs+ms:hs+ms+os]) if os else index if hard in IdxCrtSigDex: # if current sig then ondex from code must be 0 if ondex: # not zero so error raise ValueError(f"Invalid ondex={ondex} for code={hard}.") else: # zero so set to None ondex = None if not fs: # compute fs from size chars in ss part of code if cs % 4: raise ValidationError(f"Whole code size not multiple of 4 for " f"variable length material. cs={cs}.") if os != 0: raise ValidationError(f"Non-zero other index size for " f"variable length material. os={os}.") fs = (index * 4) + cs bfs = sceil(fs * 3 / 4) # bfs is min bytes to hold fs sextets if len(qb2) < bfs: # need more bytes raise ShortageError("Need {} more bytes.".format(bfs - len(qb2))) qb2 = qb2[:bfs] # extract qb2 fully qualified primitive code plus material # check for non-zeroed prepad bits or lead bytes ps = cs % 4 # code pad size ps = cs mod 4 pbs = 2 * (ps if ps else ls) # pad bit size in bits if ps: # ps. IF ps THEN not ls (lead) and vice versa OR not ps and not ls # convert last byte of code bytes in which are pad bits to int pi = (int.from_bytes(qb2[bcs-1:bcs], "big")) if pi & (2 ** pbs - 1 ): # masked pad bits non-zero raise ValueError(f"Non zeroed pad bits = " f"{pi & (2 ** pbs - 1 ):>08b} in 0x{pi:02x}.") else: # not ps. IF not ps THEN may or may not be ls (lead) li = int.from_bytes(qb2[bcs:bcs+ls], "big") # lead as int if li: # pre pad lead bytes must be zero if ls == 1: raise ValueError(f"Non zeroed lead byte = 0x{li:02x}.") else: raise ValueError(f"Non zeroed lead bytes = 0x{li:02x}.") raw = qb2[(bcs + ls):] # strip code and leader bytes from qb2 to get raw if len(raw) != (len(qb2) - bcs - ls): # exact lengths raise ConversionError(f"Improperly qualified material = {qb2}") self._code = hard self._index = index self._ondex = ondex self._raw = bytes(raw) # must be bytes for crypto ops and not bytearray mutable
[docs] class Siger(Indexer): """ Siger is subclass of Indexer, indexed signature material, Adds .verfer property which is instance of Verfer that provides associated signature verifier. See Indexer for inherited attributes and properties: Attributes: Properties: verfer (Verfer): instance if any provides public verification key Methods: Hidden: _verfer (Verfer): value for .verfer property """
[docs] def __init__(self, verfer=None, **kwa): """Initialze instance Parameters: See Matter for inherted parameters verfer (Verfer): instance if any provides public verification key """ super(Siger, self).__init__(**kwa) if self.code not in IdxSigDex: raise ValidationError("Invalid code = {} for Siger." "".format(self.code)) self.verfer = verfer
@property def verfer(self): """ Property verfer: Returns Verfer instance Assumes ._verfer is correctly assigned """ return self._verfer @verfer.setter def verfer(self, verfer): """ verfer property setter """ self._verfer = verfer