Encode credential attribute value, leaving any (stringified) int32 alone: indy-sdk predicates
    operate on int32 values properly only when their encoded values match their raw values.

    To disambiguate for decoding, the operation reserves a sentinel for the null value and otherwise adds
    2**31 to any non-trivial transform of a non-int32 input, then prepends a digit marking the input type:
    * 1: string
    * 2: boolean
    * 3: non-32-bit integer
    * 4: floating point
    * 9: other (stringifiable)

    :param raw: raw value to encode
    :return: encoded value

def encode(raw: Any) -> str:
    """
    Encode credential attribute value, leaving any (stringified) int32 alone: indy-sdk predicates
    operate on int32 values properly only when their encoded values match their raw values.

    To disambiguate for decoding, the operation reserves a sentinel for the null value and otherwise adds
    2**31 to any non-trivial transform of a non-int32 input, then prepends a digit marking the input type:
    * 1: string
    * 2: boolean
    * 3: non-32-bit integer
    * 4: floating point
    * 9: other (stringifiable)

    :param raw: raw value to encode
    :return: encoded value
    """

    if raw is None:
        return str(I32_BOUND)  # sentinel

    stringified = str(raw)
    if isinstance(raw, bool):
        return '{}{}'.format(
            ENCODE_PREFIX[bool],
            I32_BOUND + 2 if raw else I32_BOUND + 1)  # decode gotcha: python bool('False') = True; use 2 sentinels
    if isinstance(raw, int) and -I32_BOUND <= raw < I32_BOUND:
        return stringified  # it's an i32, leave it (as numeric string)

    hexed = '{}{}'.format(
        ENCODE_PREFIX.get(type(raw), ENCODE_PREFIX[None]),
        str(int.from_bytes(hexlify(stringified.encode()), 'big') + I32_BOUND))

    return hexed