Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor clean simplify fix Matter._exfil ._bexfil with more tests support for special soft codes #731

Merged
merged 3 commits into from
Mar 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 48 additions & 45 deletions src/keri/core/coring.py
Original file line number Diff line number Diff line change
Expand Up @@ -677,6 +677,7 @@ class Matter:
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

Hidden:
_code (str): value for .code property
Expand Down Expand Up @@ -831,7 +832,7 @@ def __init__(self, raw=None, code=MtrDex.Ed25519N, rize=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
soft (str | None): soft part for special codes
soft (str | bytes): soft part for special codes
strip (bool): True means strip (delete) matter from input stream
bytearray after parsing qb64b or qb2. False means do not strip

Expand All @@ -846,6 +847,9 @@ def __init__(self, raw=None, code=MtrDex.Ed25519N, rize=None,
.raw and .code and .size and .rsize

"""
if hasattr(soft, "decode"): # make soft str
soft = soft.decode("utf-8")

if raw is not None: # raw provided but may be empty
if not code:
raise EmptyMaterialError(f"Improper initialization need either "
Expand Down Expand Up @@ -913,6 +917,8 @@ def __init__(self, raw=None, code=MtrDex.Ed25519N, rize=None,

if not Reb64.match(soft.encode("utf-8")):
raise InvalidSoftError(f"Non Base64 chars in {soft=}.")
else:
soft = '' # must be empty when ss == 0


raw = raw[:rize] # copy only exact size from raw stream
Expand Down Expand Up @@ -997,14 +1003,13 @@ def _special(cls, code):
"""
Returns:
special (bool): True when code has special soft i.e. when
fs is not None and ss > 0 and fs = hs + ss and ls = 0
i.e. (fs fixed and soft not empty and raw is empty and no lead)
fs is not None and ss > 0
False otherwise

"""
hs, ss, fs, ls = cls.Sizes[code]

return (fs is not None and ss > 0 and fs == hs + ss and ls == 0)
return (fs is not None and ss > 0)


@property
Expand Down Expand Up @@ -1167,6 +1172,18 @@ def special(self):
"""
return self._special(self.code)

@property
def composable(self):
"""
composable (bool): True when both .qb64b and .qb2 are 24 bit aligned and
round trip using encodeB64 and decodeB64.
False otherwise
"""
qb64b = self.qb64b
qb2 = self.qb2
return (len(qb64b) % 4 == 0 and len(qb2) % 3 == 0 and
encodeB64(qb2) == qb64b and decodeB64(qb64b) == qb2)


def _infil(self):
"""
Expand All @@ -1185,8 +1202,6 @@ def _infil(self):
InvalidCodeSizeError(f"Invalid full code={both} for sizes {hs=} and"
f" {ss=}.")



if not fs: # variable sized
# Tests on .Sizes table must ensure ls in (0,1,2) and cs % 4 == 0 but
# can't know the variable size. So instance methods must ensure that
Expand Down Expand Up @@ -1315,30 +1330,19 @@ def _exfil(self, qb64b):
if hasattr(qb64b, "encode"): # only convert extracted chars from stream
qb64b = qb64b.encode("utf-8")

# check for non-zeroed pad bits or lead bytes
ps = cs % 4 # code pad size ps = cs mod 4 assumes cs mod 4 != 3
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, paw is bytes so raw is bytes
# check for non-zeroed pad bits and/or lead bytes
# net prepad ps == cs % 4 (remainer). Assumes ps != 3 i.e ps in (0,1,2)
# To ensure number of prepad bytes and prepad chars are same.
# need net prepad chars ps to invert using decodeB64 of lead + raw

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:] # paw is bytes so raw is bytes
ps = cs % 4 # net prepad bytes to ensure 24 bit align when encodeB64
base = ps * b'A' + qb64b[cs:] # prepad ps 'A's to B64 of (lead + raw)
paw = decodeB64(base) # now should have ps + ls leading sextexts of zeros
raw = paw[ps+ls:] # remove prepad midpat bytes to invert back to raw
# ensure midpad bytes are zero
pi = int.from_bytes(paw[:ps+ls], "big")
if pi != 0:
raise ConversionError(f"Nonzero midpad bytes=0x{pi:0{(ps + ls) * 2}x}.")

if len(raw) != ((len(qb64b) - cs) * 3 // 4) - ls: # exact lengths
raise ConversionError(f"Improperly qualified material = {qb64b}")
Expand Down Expand Up @@ -1403,22 +1407,21 @@ def _bexfil(self, qb2):
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}.")


# 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
# get pad bits in last byte of full code
pi = (int.from_bytes(qb2[bcs-1:bcs], "big")) # convert byte to int
pi = pi & (2 ** pbs - 1 ) # mask with 1's in pad bit locations
if pi: # not zero so raise error
raise ConversionError(f"Nonzero code mid pad bits=0b{pi:0{pbs}b}.")

# check nonzero leading mid pad lead bytes in lead + raw
li = int.from_bytes(qb2[bcs:bcs+ls], "big") # lead as int
if li: # midpad lead bytes must be zero
raise ConversionError(f"Nonzero lead midpad bytes=0x{li:0{ls*2}x}.")

# strip code and leader bytes from qb2 to get raw
raw = qb2[(bcs + ls):] # may be empty
Expand Down
3 changes: 2 additions & 1 deletion tests/app/test_keeping.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

from hio.base import doing

from keri import kering
from keri.help import helping
from keri.core import coring
from keri.core.coring import IdrDex
Expand Down Expand Up @@ -708,7 +709,7 @@ def test_manager():

with keeping.openKS() as keeper:

with pytest.raises(ValueError):
with pytest.raises(kering.ConversionError):
#test invalid qb64 of Salt
manager = keeping.Manager(ks=keeper, salt='0AzwMTIzNDU2Nzg5YWJjZGVm')

Expand Down
Loading
Loading