"""
Python wrapper of the botan crypto library
https://botan.randombit.net

(C) 2015,2017,2018,2019,2023 Jack Lloyd
(C) 2015 Uri  Blumenthal (extensions and patches)
(C) 2024 Amos Treiber - Rohde & Schwarz Cybersecurity
(C) 2024,2026 René Meusel - Rohde & Schwarz Cybersecurity
(C) 2025 Dominik Schricker

Botan is released under the Simplified BSD License (see license.txt)

This module uses the ctypes module and is usable by programs running
under at least CPython 3.x, and PyPy

It uses botan's ffi module, which exposes a C API.
"""

from __future__ import annotations
from ctypes import CDLL, CFUNCTYPE, POINTER, byref, create_string_buffer, \
    c_void_p, c_size_t, c_uint8, c_uint32, c_uint64, c_int, c_uint, c_char, c_char_p, addressof, Array, \
    cast, memmove, py_object, string_at
from typing import Callable, Any, Union

from sys import platform
from time import strptime, mktime, time as system_time
from binascii import hexlify
from datetime import datetime
from collections.abc import Iterable
from enum import IntEnum

# This Python module requires the FFI API version introduced in Botan 3.13.0
#
# 3.13.0 - botan_hash_security_level, SPAKE2+
# 3.12.0 - EcScalar/EcPoint, DRBG
# 3.11.0 - XOF API
# 3.10.0 - introduced botan_pubkey_load_ec*_sec1()
BOTAN_FFI_VERSION = 20260811  #: The minimum FFI API version this module requires of the library.


class BotanException(Exception):
    """Base exception for all exceptions raised from this module"""

    def __init__(self, message, rc=0):
        """Create an exception with ``message``. If ``rc`` is a nonzero library error
        code, the description of that error plus the library's most recent exception
        message are appended."""

        self.__rc = rc

        if rc == 0:
            super().__init__(message)
        else:
            exn_msg = _DLL.botan_error_last_exception_message().decode('ascii')
            err_descr = _DLL.botan_error_description(rc).decode('ascii')

            formatted_msg = "%s: %d (%s)" % (message, rc, err_descr)
            if exn_msg != "":
                formatted_msg += ': ' + exn_msg

            super().__init__(formatted_msg)

    def error_code(self) -> int:
        """Returns the library error code associated with this exception, or zero if there is none"""
        return self.__rc

#
# Module initialization
#

def _load_botan_dll(expected_version):

    possible_dll_names = []

    if platform in ['win32', 'cygwin', 'msys']:
        possible_dll_names.append('botan-3.dll')
        possible_dll_names.append('libbotan-3.dll')
        possible_dll_names.append('botan.dll')
    elif platform in ['darwin', 'macos']:
        possible_dll_names.append('libbotan-3.dylib')
    else:
        # assumed to be some Unix/Linux system
        possible_dll_names.append('libbotan-3.so')

        min_minor = 8 # minimum supported FFI
        max_minor = 32 # arbitrary but probably large enough
        possible_dll_names += ['libbotan-3.so.%d' % (v) for v in reversed(range(min_minor, max_minor))]

    for dll_name in possible_dll_names:
        try:
            dll = CDLL(dll_name)
            if hasattr(dll, 'botan_ffi_supports_api'):
                dll.botan_ffi_supports_api.argtypes = [c_uint32]
                dll.botan_ffi_supports_api.restype = c_int
                if dll.botan_ffi_supports_api(expected_version) == 0:
                    return dll
        except OSError:
            pass

    raise BotanException("Could not find a usable Botan shared object library")

_VIEW_BIN_CALLBACK = CFUNCTYPE(c_int, c_void_p, POINTER(c_char), c_size_t)
_VIEW_STR_CALLBACK = CFUNCTYPE(c_int, c_void_p, c_char_p, c_size_t)
_RNG_GET_CALLBACK = CFUNCTYPE(c_int, c_void_p, POINTER(c_uint8), c_size_t)
_RNG_ADD_ENTROPY_CALLBACK = CFUNCTYPE(c_int, c_void_p, POINTER(c_uint8), c_size_t)
_RNG_DESTROY_CALLBACK = CFUNCTYPE(None, c_void_p)

def _errcheck(rc, fn, _args):
    # This errcheck should only be used for int-returning functions
    assert isinstance(rc, int)

    if rc >= 0 or rc in fn.allowed_errors:
        return rc
    raise BotanException('%s failed' % (fn.__name__), rc)

def _set_prototypes(dll):
    def ffi_api(fn, args, allowed_errors=None):
        if allowed_errors is None:
            allowed_errors = [-10]
        fn.argtypes = args
        fn.restype = c_int
        fn.errcheck = _errcheck
        fn.allowed_errors = allowed_errors

    dll.botan_version_string.argtypes = []
    dll.botan_version_string.restype = c_char_p

    dll.botan_version_string.argtypes = []
    dll.botan_version_string.restype = c_char_p

    dll.botan_version_major.argtypes = []
    dll.botan_version_major.restype = c_uint32

    dll.botan_version_minor.argtypes = []
    dll.botan_version_minor.restype = c_uint32

    dll.botan_version_patch.argtypes = []
    dll.botan_version_patch.restype = c_uint32

    dll.botan_ffi_api_version.argtypes = []
    dll.botan_ffi_api_version.restype = c_uint32

    dll.botan_error_description.argtypes = [c_int]
    dll.botan_error_description.restype = c_char_p

    dll.botan_error_last_exception_message.argtypes = []
    dll.botan_error_last_exception_message.restype = c_char_p

    # These are generated using src/scripts/dev_tools/gen_ffi_decls.py:
    ffi_api(dll.botan_constant_time_compare, [c_void_p, c_void_p, c_size_t], [-1])
    ffi_api(dll.botan_scrub_mem, [c_void_p, c_size_t])

    ffi_api(dll.botan_hex_encode, [c_char_p, c_size_t, c_char_p, c_uint32])
    ffi_api(dll.botan_hex_decode, [c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])

    ffi_api(dll.botan_base64_encode, [c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_base64_decode, [c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])

    #  RNG
    ffi_api(dll.botan_rng_init, [c_void_p, c_char_p])
    ffi_api(dll.botan_rng_init_custom, [c_void_p, c_char_p, c_void_p, _RNG_GET_CALLBACK, _RNG_ADD_ENTROPY_CALLBACK, _RNG_DESTROY_CALLBACK])
    ffi_api(dll.botan_rng_get, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_rng_reseed, [c_void_p, c_size_t])
    ffi_api(dll.botan_rng_reseed_from_rng, [c_void_p, c_void_p, c_size_t])
    ffi_api(dll.botan_rng_add_entropy, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_rng_init_drbg, [c_void_p, c_char_p, c_char_p, c_size_t])
    ffi_api(dll.botan_rng_generate_with_input, [c_void_p, c_char_p, c_size_t, c_char_p, c_size_t])
    ffi_api(dll.botan_rng_destroy, [c_void_p])

    #  HASH
    ffi_api(dll.botan_hash_init, [c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_hash_copy_state, [c_void_p, c_void_p])
    ffi_api(dll.botan_hash_output_length, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_hash_block_size, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_hash_security_level, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_hash_update, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_hash_final, [c_void_p, c_char_p])
    ffi_api(dll.botan_hash_clear, [c_void_p])
    ffi_api(dll.botan_hash_destroy, [c_void_p])
    ffi_api(dll.botan_hash_name, [c_void_p, c_char_p, POINTER(c_size_t)])

    # XOF
    ffi_api(dll.botan_xof_init, [c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_xof_copy_state, [c_void_p, c_void_p])
    ffi_api(dll.botan_xof_block_size, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_xof_name, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_xof_accepts_input, [c_void_p])
    ffi_api(dll.botan_xof_clear, [c_void_p])
    ffi_api(dll.botan_xof_update, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_xof_output, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_xof_destroy, [c_void_p])

    #  MAC
    ffi_api(dll.botan_mac_init, [c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_mac_output_length, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_mac_set_key, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_mac_set_nonce, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_mac_update, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_mac_final, [c_void_p, c_char_p])
    ffi_api(dll.botan_mac_clear, [c_void_p])
    ffi_api(dll.botan_mac_name, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_mac_get_keyspec, [c_void_p, POINTER(c_size_t), POINTER(c_size_t), POINTER(c_size_t)])
    ffi_api(dll.botan_mac_destroy, [c_void_p])

    #  CIPHER
    ffi_api(dll.botan_cipher_init, [c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_cipher_name, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_output_length, [c_void_p, c_size_t, POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_valid_nonce_length, [c_void_p, c_size_t])
    ffi_api(dll.botan_cipher_get_tag_length, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_get_default_nonce_length, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_is_authenticated, [c_void_p])
    ffi_api(dll.botan_cipher_requires_entire_message, [c_void_p])
    ffi_api(dll.botan_cipher_get_update_granularity, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_get_ideal_update_granularity, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_query_keylen, [c_void_p, POINTER(c_size_t), POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_get_keyspec, [c_void_p, POINTER(c_size_t), POINTER(c_size_t), POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_set_key, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_cipher_reset, [c_void_p])
    ffi_api(dll.botan_cipher_set_associated_data, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_cipher_start, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_cipher_update,
            [c_void_p, c_uint32, c_char_p, c_size_t, POINTER(c_size_t), c_char_p, c_size_t, POINTER(c_size_t)])
    ffi_api(dll.botan_cipher_clear, [c_void_p])
    ffi_api(dll.botan_cipher_destroy, [c_void_p])

    ffi_api(dll.botan_pbkdf,
            [c_char_p, c_char_p, c_size_t, c_char_p, c_char_p, c_size_t, c_size_t])
    ffi_api(dll.botan_pbkdf_timed,
            [c_char_p, c_char_p, c_size_t, c_char_p, c_char_p, c_size_t, c_size_t, POINTER(c_size_t)])

    ffi_api(dll.botan_pwdhash,
            [c_char_p, c_size_t, c_size_t, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t])
    ffi_api(dll.botan_pwdhash_timed,
            [c_char_p, c_uint32, POINTER(c_size_t), POINTER(c_size_t), POINTER(c_size_t), c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t])

    ffi_api(dll.botan_scrypt,
            [c_char_p, c_size_t, c_char_p, c_char_p, c_size_t, c_size_t, c_size_t, c_size_t])

    ffi_api(dll.botan_kdf,
            [c_char_p, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t])

    #  BLOCK
    ffi_api(dll.botan_block_cipher_init, [c_void_p, c_char_p])
    ffi_api(dll.botan_block_cipher_destroy, [c_void_p])
    ffi_api(dll.botan_block_cipher_clear, [c_void_p])
    ffi_api(dll.botan_block_cipher_set_key, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_block_cipher_block_size, [c_void_p])
    ffi_api(dll.botan_block_cipher_encrypt_blocks, [c_void_p, c_char_p, c_char_p, c_size_t])
    ffi_api(dll.botan_block_cipher_decrypt_blocks, [c_void_p, c_char_p, c_char_p, c_size_t])
    ffi_api(dll.botan_block_cipher_name, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_block_cipher_get_keyspec, [c_void_p, POINTER(c_size_t), POINTER(c_size_t), POINTER(c_size_t)])

    #  MP
    ffi_api(dll.botan_mp_init, [c_void_p])
    ffi_api(dll.botan_mp_destroy, [c_void_p])
    ffi_api(dll.botan_mp_to_hex, [c_void_p, c_char_p])
    ffi_api(dll.botan_mp_view_hex, [c_void_p, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_mp_to_str, [c_void_p, c_uint8, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_mp_view_str, [c_void_p, c_uint8, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_mp_clear, [c_void_p])
    ffi_api(dll.botan_mp_set_from_int, [c_void_p, c_int])
    ffi_api(dll.botan_mp_set_from_mp, [c_void_p, c_void_p])
    ffi_api(dll.botan_mp_set_from_str, [c_void_p, c_char_p])
    ffi_api(dll.botan_mp_set_from_radix_str, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_mp_num_bits, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_mp_num_bytes, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_mp_to_bin, [c_void_p, c_char_p])
    ffi_api(dll.botan_mp_from_bin, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_mp_to_uint32, [c_void_p, POINTER(c_uint32)])
    ffi_api(dll.botan_mp_is_positive, [c_void_p])
    ffi_api(dll.botan_mp_is_negative, [c_void_p])
    ffi_api(dll.botan_mp_flip_sign, [c_void_p])
    ffi_api(dll.botan_mp_is_zero, [c_void_p])
    ffi_api(dll.botan_mp_is_odd, [c_void_p])
    ffi_api(dll.botan_mp_is_even, [c_void_p])
    ffi_api(dll.botan_mp_add_u32, [c_void_p, c_void_p, c_uint32])
    ffi_api(dll.botan_mp_sub_u32, [c_void_p, c_void_p, c_uint32])
    ffi_api(dll.botan_mp_add, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_sub, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_mul, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_div, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_mod_mul, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_equal, [c_void_p, c_void_p])
    ffi_api(dll.botan_mp_cmp, [POINTER(c_int), c_void_p, c_void_p])
    ffi_api(dll.botan_mp_swap, [c_void_p, c_void_p])
    ffi_api(dll.botan_mp_powmod, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_lshift, [c_void_p, c_void_p, c_size_t])
    ffi_api(dll.botan_mp_rshift, [c_void_p, c_void_p, c_size_t])
    ffi_api(dll.botan_mp_mod_inverse, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_rand_bits, [c_void_p, c_void_p, c_size_t])
    ffi_api(dll.botan_mp_rand_range, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_gcd, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_mp_is_prime, [c_void_p, c_void_p, c_size_t])
    ffi_api(dll.botan_mp_get_bit, [c_void_p, c_size_t])
    ffi_api(dll.botan_mp_set_bit, [c_void_p, c_size_t])
    ffi_api(dll.botan_mp_clear_bit, [c_void_p, c_size_t])

    ffi_api(dll.botan_bcrypt_generate,
            [c_char_p, POINTER(c_size_t), c_char_p, c_void_p, c_size_t, c_uint32])
    ffi_api(dll.botan_bcrypt_is_valid, [c_char_p, c_char_p])

    # OID
    ffi_api(dll.botan_oid_destroy, [c_void_p])
    ffi_api(dll.botan_oid_from_string, [c_void_p, c_char_p])
    ffi_api(dll.botan_oid_register, [c_void_p, c_char_p])
    ffi_api(dll.botan_oid_view_string, [c_void_p, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_oid_view_name, [c_void_p, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_oid_equal, [c_void_p, c_void_p])
    ffi_api(dll.botan_oid_cmp, [POINTER(c_int), c_void_p, c_void_p])

    # EC Group
    ffi_api(dll.botan_ec_group_destroy, [c_void_p])
    ffi_api(dll.botan_ec_group_supports_application_specific_group, [POINTER(c_int)])
    ffi_api(dll.botan_ec_group_supports_named_group, [c_char_p, POINTER(c_int)])
    ffi_api(dll.botan_ec_group_from_params,
            [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_from_ber, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_ec_group_from_pem, [c_void_p, c_char_p])
    ffi_api(dll.botan_ec_group_from_oid, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_from_name, [c_void_p, c_char_p])
    ffi_api(dll.botan_ec_group_unregister, [c_void_p])
    ffi_api(dll.botan_ec_group_view_der, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_ec_group_view_pem, [c_void_p, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_ec_group_get_curve_oid, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_get_p, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_get_a, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_get_b, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_get_g_x, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_get_g_y, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_get_order, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_group_equal, [c_void_p, c_void_p])

    # EC Points and Scalars
    ffi_api(dll.botan_ec_scalar_destroy, [c_void_p])
    ffi_api(dll.botan_ec_scalar_random, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_ec_scalar_from_mp, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_ec_scalar_to_mp, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_point_destroy, [c_void_p])
    ffi_api(dll.botan_ec_point_identity, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_point_generator, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_point_from_xy, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_ec_point_from_bytes, [c_void_p, c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_ec_point_view_x_bytes, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_ec_point_view_y_bytes, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_ec_point_view_xy_bytes, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_ec_point_view_uncompressed, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_ec_point_view_compressed, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_ec_point_is_identity, [c_void_p])
    ffi_api(dll.botan_ec_point_equal, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_point_negate, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_point_add, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_ec_point_mul, [c_void_p, c_void_p, c_void_p, c_void_p])

    #  PUBKEY
    ffi_api(dll.botan_privkey_create, [c_void_p, c_char_p, c_char_p, c_void_p])
    ffi_api(dll.botan_ec_privkey_create, [c_void_p, c_char_p, c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_check_key, [c_void_p, c_void_p, c_uint32], [-1])
    ffi_api(dll.botan_privkey_create_rsa, [c_void_p, c_void_p, c_size_t])
    ffi_api(dll.botan_privkey_create_ecdsa, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_create_ecdh, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_create_mceliece, [c_void_p, c_void_p, c_size_t, c_size_t])
    ffi_api(dll.botan_privkey_create_dh, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_create_dsa, [c_void_p, c_void_p, c_size_t, c_size_t])
    ffi_api(dll.botan_privkey_create_elgamal, [c_void_p, c_void_p, c_size_t, c_size_t])
    ffi_api(dll.botan_privkey_load,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_char_p])
    ffi_api(dll.botan_privkey_destroy, [c_void_p])

    ffi_api(dll.botan_privkey_view_der, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_privkey_view_pem, [c_void_p, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_privkey_view_raw, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])

    ffi_api(dll.botan_privkey_algo_name, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_privkey_export_encrypted,
            [c_void_p, c_char_p, POINTER(c_size_t), c_void_p, c_char_p, c_char_p, c_uint32])

    ffi_api(dll.botan_privkey_export_encrypted_pbkdf_msec,
            [c_void_p, c_char_p, POINTER(c_size_t), c_void_p, c_char_p, c_uint32, POINTER(c_size_t), c_char_p, c_char_p, c_uint32])
    ffi_api(dll.botan_privkey_export_encrypted_pbkdf_iter,
            [c_void_p, c_char_p, POINTER(c_size_t), c_void_p, c_char_p, c_size_t, c_char_p, c_char_p, c_uint32])

    ffi_api(dll.botan_privkey_view_encrypted_der,
            [c_void_p, c_void_p, c_char_p, c_char_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_privkey_view_encrypted_pem,
            [c_void_p, c_void_p, c_char_p, c_char_p, c_char_p, c_size_t, c_void_p, _VIEW_STR_CALLBACK])

    ffi_api(dll.botan_privkey_view_encrypted_der_timed,
            [c_void_p, c_void_p, c_char_p, c_char_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_privkey_view_encrypted_pem_timed,
            [c_void_p, c_void_p, c_char_p, c_char_p, c_char_p, c_size_t, c_void_p, _VIEW_STR_CALLBACK])

    ffi_api(dll.botan_privkey_export_pubkey, [c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_load, [c_void_p, c_char_p, c_size_t])

    ffi_api(dll.botan_pubkey_view_der, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_pubkey_view_pem, [c_void_p, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_pubkey_view_raw, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])

    ffi_api(dll.botan_pubkey_algo_name, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_pubkey_check_key, [c_void_p, c_void_p, c_uint32], [-1])
    ffi_api(dll.botan_pubkey_estimated_strength, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_pubkey_fingerprint, [c_void_p, c_char_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_pubkey_destroy, [c_void_p])
    ffi_api(dll.botan_pubkey_get_field, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_get_field, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_oid, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_oid, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_stateful_operation, [c_void_p, POINTER(c_int)])
    ffi_api(dll.botan_privkey_remaining_operations, [c_void_p, POINTER(c_uint64)])
    ffi_api(dll.botan_privkey_load_rsa, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_load_rsa_pkcs1, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_privkey_rsa_get_p, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_rsa_get_q, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_rsa_get_d, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_rsa_get_n, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_rsa_get_e, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_rsa_get_privkey, [c_void_p, c_char_p, POINTER(c_size_t), c_uint32])
    ffi_api(dll.botan_pubkey_load_rsa, [c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_load_rsa_pkcs1, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_pubkey_rsa_get_e, [c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_rsa_get_n, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_load_dsa,
            [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_load_dsa,
            [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_dsa_get_x, [c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_dsa_get_p, [c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_dsa_get_q, [c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_dsa_get_g, [c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_dsa_get_y, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_load_dh, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_load_dh, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_pubkey_load_elgamal, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_load_elgamal, [c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_ec_privkey_get_private_key, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_privkey_get_group, [c_void_p, c_void_p])
    ffi_api(dll.botan_ec_pubkey_get_group, [c_void_p, c_void_p])
    ffi_api(dll.botan_privkey_load_ed25519, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_ed25519, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_ed25519_get_privkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_ed25519_get_pubkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_load_ed448, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_ed448, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_ed448_get_privkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_ed448_get_pubkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_load_x25519, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_x25519, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_x25519_get_privkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_x25519_get_pubkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_load_x448, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_x448, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_x448_get_privkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_x448_get_pubkey, [c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_load_ml_dsa, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_pubkey_load_ml_dsa, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_privkey_load_slh_dsa, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_pubkey_load_slh_dsa, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_privkey_load_kyber, [c_void_p, c_char_p, c_int])
    ffi_api(dll.botan_pubkey_load_kyber, [c_void_p, c_char_p, c_int])
    ffi_api(dll.botan_privkey_view_kyber_raw_key, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_pubkey_view_kyber_raw_key, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_privkey_load_ml_kem, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_pubkey_load_ml_kem, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_privkey_load_frodokem, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_pubkey_load_frodokem, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_privkey_load_classic_mceliece, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_pubkey_load_classic_mceliece, [c_void_p, c_void_p, c_int, c_char_p])
    ffi_api(dll.botan_privkey_load_ecdsa, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_ecdsa, [c_void_p, c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_ecdsa_sec1, [c_void_p, c_void_p, c_size_t, c_char_p])
    ffi_api(dll.botan_pubkey_load_ecdh, [c_void_p, c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_load_ecdh, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_ecdh_sec1, [c_void_p, c_void_p, c_size_t, c_char_p])
    ffi_api(dll.botan_pubkey_load_sm2, [c_void_p, c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_sm2_sec1, [c_void_p, c_void_p, c_size_t, c_char_p])
    ffi_api(dll.botan_privkey_load_sm2, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_load_sm2_enc, [c_void_p, c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_privkey_load_sm2_enc, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pubkey_sm2_compute_za,
            [c_char_p, POINTER(c_size_t), c_char_p, c_char_p, c_void_p])
    ffi_api(dll.botan_pubkey_view_ec_public_point,
            [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_pubkey_ecc_key_used_explicit_encoding, [c_void_p], [-32])

    #  PK
    ffi_api(dll.botan_pk_op_encrypt_create, [c_void_p, c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_pk_op_encrypt_destroy, [c_void_p])
    ffi_api(dll.botan_pk_op_encrypt_output_length, [c_void_p, c_size_t, POINTER(c_size_t)])
    ffi_api(dll.botan_pk_op_encrypt,
            [c_void_p, c_void_p, c_char_p, POINTER(c_size_t), c_char_p, c_size_t])
    ffi_api(dll.botan_pk_op_decrypt_create, [c_void_p, c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_pk_op_decrypt_destroy, [c_void_p])
    ffi_api(dll.botan_pk_op_decrypt_output_length, [c_void_p, c_size_t, POINTER(c_size_t)])
    ffi_api(dll.botan_pk_op_decrypt,
            [c_void_p, c_char_p, POINTER(c_size_t), c_char_p, c_size_t])
    ffi_api(dll.botan_pk_op_sign_create, [c_void_p, c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_pk_op_sign_destroy, [c_void_p])
    ffi_api(dll.botan_pk_op_sign_output_length, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_pk_op_sign_update, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_pk_op_sign_finish, [c_void_p, c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_pk_op_verify_create, [c_void_p, c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_pk_op_verify_destroy, [c_void_p])
    ffi_api(dll.botan_pk_op_verify_update, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_pk_op_verify_finish, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_pk_op_key_agreement_create, [c_void_p, c_void_p, c_char_p, c_uint32])
    ffi_api(dll.botan_pk_op_key_agreement_destroy, [c_void_p])
    ffi_api(dll.botan_pk_op_key_agreement_view_public, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])

    ffi_api(dll.botan_pk_op_key_agreement_size, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_pk_op_key_agreement,
            [c_void_p, c_char_p, POINTER(c_size_t), c_char_p, c_size_t, c_char_p, c_size_t])

    ffi_api(dll.botan_pkcs_hash_id, [c_char_p, c_char_p, POINTER(c_size_t)])

    ffi_api(dll.botan_pk_op_kem_encrypt_create, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pk_op_kem_encrypt_destroy, [c_void_p])

    ffi_api(dll.botan_pk_op_kem_encrypt_shared_key_length, [c_void_p, c_size_t, POINTER(c_size_t)])
    ffi_api(dll.botan_pk_op_kem_encrypt_encapsulated_key_length, [c_void_p, POINTER(c_size_t)])

    ffi_api(dll.botan_pk_op_kem_encrypt_create_shared_key,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_size_t,
             c_char_p, POINTER(c_size_t), c_char_p, POINTER(c_size_t)])

    ffi_api(dll.botan_pk_op_kem_decrypt_create, [c_void_p, c_void_p, c_char_p])
    ffi_api(dll.botan_pk_op_kem_decrypt_destroy, [c_void_p])

    ffi_api(dll.botan_pk_op_kem_decrypt_shared_key_length, [c_void_p, c_size_t, POINTER(c_size_t)])

    ffi_api(dll.botan_pk_op_kem_decrypt_shared_key,
            [c_void_p, c_char_p, c_size_t, c_char_p, c_size_t, c_size_t, c_char_p, POINTER(c_size_t)])

    #  X509
    ffi_api(dll.botan_x509_cert_load, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_x509_cert_load_file, [c_void_p, c_char_p])
    ffi_api(dll.botan_x509_cert_destroy, [c_void_p])
    ffi_api(dll.botan_x509_cert_dup, [c_void_p, c_void_p])
    ffi_api(dll.botan_x509_cert_get_time_starts, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_get_time_expires, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_not_before, [c_void_p, POINTER(c_uint64)])
    ffi_api(dll.botan_x509_cert_not_after, [c_void_p, POINTER(c_uint64)])
    ffi_api(dll.botan_x509_cert_get_fingerprint, [c_void_p, c_char_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_get_serial_number, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_get_authority_key_id, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_get_subject_key_id, [c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_view_public_key_bits, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_x509_cert_get_public_key, [c_void_p, c_void_p])
    ffi_api(dll.botan_x509_cert_get_issuer_dn,
            [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_get_subject_dn,
            [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_cert_view_as_string, [c_void_p, c_void_p, _VIEW_STR_CALLBACK])
    ffi_api(dll.botan_x509_cert_allowed_usage, [c_void_p, c_uint])
    ffi_api(dll.botan_x509_cert_hostname_match, [c_void_p, c_char_p], [-1])
    ffi_api(dll.botan_x509_cert_verify,
            [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64])

    dll.botan_x509_cert_validation_status.argtypes = [c_int]
    dll.botan_x509_cert_validation_status.restype = c_char_p

    # X509 Extensions
    ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_counts, [c_void_p, POINTER(c_size_t), POINTER(c_size_t)])
    ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_family,
            [c_void_p, c_int, c_size_t, POINTER(c_int), POINTER(c_uint8), POINTER(c_int), POINTER(c_size_t)])
    ffi_api(dll.botan_x509_ext_ip_addr_blocks_get_address,
            [c_void_p, c_int, c_size_t, c_size_t, c_char_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_ext_as_blocks_get_info, [c_void_p, c_int, POINTER(c_int), POINTER(c_size_t)])
    ffi_api(dll.botan_x509_ext_as_blocks_get_entry_at,
            [c_void_p, c_int, c_size_t, POINTER(c_uint32), POINTER(c_uint32)])

    # X509 CRL
    ffi_api(dll.botan_x509_crl_load, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_x509_crl_load_file, [c_void_p, c_char_p])
    ffi_api(dll.botan_x509_crl_this_update, [c_void_p, POINTER(c_uint64)])
    ffi_api(dll.botan_x509_crl_next_update, [c_void_p, POINTER(c_uint64)])
    ffi_api(dll.botan_x509_crl_create,
            [c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint32, c_char_p, c_char_p])
    ffi_api(dll.botan_x509_crl_entry_create, [c_void_p, c_void_p, c_int])
    ffi_api(dll.botan_x509_crl_update,
            [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p, c_uint64, c_uint32, c_void_p, c_size_t, c_char_p, c_char_p])
    ffi_api(dll.botan_x509_crl_verify_signature, [c_void_p, c_void_p])
    ffi_api(dll.botan_x509_crl_destroy, [c_void_p])
    ffi_api(dll.botan_x509_is_revoked, [c_void_p, c_void_p], [-1])
    ffi_api(dll.botan_x509_crl_entries, [c_void_p, c_size_t, c_void_p])
    ffi_api(dll.botan_x509_crl_entries_count, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_x509_crl_entry_reason, [c_void_p, POINTER(c_int)])
    ffi_api(dll.botan_x509_crl_entry_revocation_date, [c_void_p, POINTER(c_uint64)])
    ffi_api(dll.botan_x509_crl_entry_serial_number, [c_void_p, c_void_p])
    ffi_api(dll.botan_x509_crl_entry_view_serial_number, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_x509_crl_entry_destroy, [c_void_p])
    ffi_api(dll.botan_x509_cert_verify_with_crl,
            [POINTER(c_int), c_void_p, c_void_p, c_size_t, c_void_p, c_size_t, c_void_p, c_size_t, c_char_p, c_size_t, c_char_p, c_uint64])

    ffi_api(dll.botan_nist_kw_enc,
            [c_char_p, c_int, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_nist_kw_dec,
            [c_char_p, c_int, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])

    #  HOTP
    ffi_api(dll.botan_hotp_init,
            [c_void_p, c_char_p, c_size_t, c_char_p, c_size_t])
    ffi_api(dll.botan_hotp_destroy, [c_void_p])
    ffi_api(dll.botan_hotp_generate, [c_void_p, POINTER(c_uint32), c_uint64])
    ffi_api(dll.botan_hotp_check,
            [c_void_p, POINTER(c_uint64), c_uint32, c_uint64, c_size_t])

    #  TOTP
    ffi_api(dll.botan_totp_init,
            [c_void_p, c_char_p, c_size_t, c_char_p, c_size_t, c_size_t])
    ffi_api(dll.botan_totp_destroy, [c_void_p])
    ffi_api(dll.botan_totp_generate, [c_void_p, POINTER(c_uint32), c_uint64])
    ffi_api(dll.botan_totp_check, [c_void_p, c_uint32, c_uint64, c_size_t])

    #  FPE
    ffi_api(dll.botan_fpe_fe1_init,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_size_t, c_uint32])
    ffi_api(dll.botan_fpe_destroy, [c_void_p])
    ffi_api(dll.botan_fpe_encrypt, [c_void_p, c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_fpe_decrypt, [c_void_p, c_void_p, c_char_p, c_size_t])

    # SRP6-a
    ffi_api(dll.botan_srp6_server_session_init, [c_void_p])
    ffi_api(dll.botan_srp6_server_session_destroy, [c_void_p])
    ffi_api(dll.botan_srp6_server_session_step1,
            [c_void_p, c_char_p, c_size_t, c_char_p, c_char_p, c_void_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_srp6_server_session_step2,
            [c_void_p, c_char_p, c_size_t, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_srp6_generate_verifier,
            [c_char_p, c_char_p, c_char_p, c_size_t, c_char_p, c_char_p, c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_srp6_client_agree,
            [c_char_p, c_char_p, c_char_p, c_char_p, c_char_p, c_size_t, c_char_p, c_size_t, c_void_p,
             c_char_p, POINTER(c_size_t), c_char_p, POINTER(c_size_t)])
    ffi_api(dll.botan_srp6_group_size, [c_char_p, POINTER(c_size_t)])

    # SPAKE2+
    ffi_api(dll.botan_spake2p_params_init, [c_void_p, c_char_p])
    ffi_api(dll.botan_spake2p_params_init_custom, [c_void_p, c_void_p, c_char_p, c_size_t, c_char_p])
    ffi_api(dll.botan_spake2p_params_destroy, [c_void_p])
    ffi_api(dll.botan_spake2p_params_share_size, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_spake2p_params_confirmation_size, [c_void_p, POINTER(c_size_t)])
    ffi_api(dll.botan_spake2p_derive_secret,
            [c_void_p, c_char_p, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_spake2p_registration_record,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_spake2p_prover_init,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t])
    ffi_api(dll.botan_spake2p_prover_destroy, [c_void_p])
    ffi_api(dll.botan_spake2p_prover_generate_message, [c_void_p, c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_spake2p_prover_process_message,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_spake2p_prover_shared_secret, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_spake2p_verifier_init,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t, c_char_p, c_size_t])
    ffi_api(dll.botan_spake2p_verifier_destroy, [c_void_p])
    ffi_api(dll.botan_spake2p_verifier_process_message,
            [c_void_p, c_void_p, c_char_p, c_size_t, c_void_p, _VIEW_BIN_CALLBACK])
    ffi_api(dll.botan_spake2p_verifier_verify_confirmation, [c_void_p, c_char_p, c_size_t])
    ffi_api(dll.botan_spake2p_verifier_skip_confirmation, [c_void_p])
    ffi_api(dll.botan_spake2p_verifier_shared_secret, [c_void_p, c_void_p, _VIEW_BIN_CALLBACK])

    # ZFEC
    ffi_api(dll.botan_zfec_encode,
            [c_size_t, c_size_t, c_char_p, c_size_t, POINTER(c_char_p)])
    ffi_api(dll.botan_zfec_decode,
            [c_size_t, c_size_t, POINTER(c_size_t), POINTER(c_char_p), c_size_t, POINTER(c_char_p)])

    # TPM2
    ffi_api(dll.botan_tpm2_supports_crypto_backend, [])
    ffi_api(dll.botan_tpm2_ctx_init, [c_void_p, c_char_p], [-40])
    ffi_api(dll.botan_tpm2_ctx_init_ex, [c_void_p, c_char_p, c_char_p], [-40])
    ffi_api(dll.botan_tpm2_ctx_enable_crypto_backend, [c_void_p, c_void_p])
    ffi_api(dll.botan_tpm2_ctx_destroy, [c_void_p], [-40])
    ffi_api(dll.botan_tpm2_rng_init, [c_void_p, c_void_p, c_void_p, c_void_p, c_void_p])
    ffi_api(dll.botan_tpm2_unauthenticated_session_init, [c_void_p, c_void_p])
    ffi_api(dll.botan_tpm2_session_destroy, [c_void_p])

    return dll

#
# Load the DLL and set prototypes on it
#
_DLL = _set_prototypes(_load_botan_dll(BOTAN_FFI_VERSION))

#
# Internal utilities
#
def _call_fn_returning_sz(fn) -> int:
    sz = c_size_t(0)
    fn(byref(sz))
    return int(sz.value)

def _call_fn_returning_bool(fn) -> bool:
    res = fn()
    return res > 0

def _call_fn_returning_vec(guess, fn) -> bytes:

    buf = create_string_buffer(guess)
    buf_len = c_size_t(len(buf))

    rc = fn(buf, byref(buf_len))
    if rc == -10 and buf_len.value > len(buf):
        return _call_fn_returning_vec(buf_len.value, fn)

    assert buf_len.value <= len(buf)
    return buf.raw[0:int(buf_len.value)]

def _call_fn_returning_vec_pair(guess1, guess2, fn) -> tuple[bytes, bytes]:

    buf1 = create_string_buffer(guess1)
    buf1_len = c_size_t(len(buf1))

    buf2 = create_string_buffer(guess2)
    buf2_len = c_size_t(len(buf2))

    rc = fn(buf1, byref(buf1_len), buf2, byref(buf2_len))
    if rc == -10:
        if buf1_len.value > len(buf1):
            guess1 = buf1_len.value
        if buf2_len.value > len(buf2):
            guess2 = buf2_len.value
        return _call_fn_returning_vec_pair(guess1, guess2, fn)

    assert buf1_len.value <= len(buf1)
    assert buf2_len.value <= len(buf2)
    return (buf1.raw[0:int(buf1_len.value)], buf2.raw[0:int(buf2_len.value)])

def _call_fn_returning_str(guess, fn):
    # Assumes that anything called with this is returning plain ASCII strings
    # (base64 data, algorithm names, etc)
    v = _call_fn_returning_vec(guess, fn)
    return v.decode('ascii')[:-1]

def _call_fn_viewing_vec(fn) -> bytes:
    # The viewer callback and its output holder are call-local so that
    # concurrent calls from multiple threads (ctypes releases the GIL across
    # the C call) cannot clobber each other's exported data.
    output = []

    @_VIEW_BIN_CALLBACK
    def viewer(_ctx, buf_val, buf_len):
        output.append(buf_val[0:buf_len])
        return 0

    fn(None, viewer)
    if not output:
        raise BotanException('View callback was not invoked')
    return output[0]

def _call_fn_viewing_str(fn) -> str:
    output = []

    @_VIEW_STR_CALLBACK
    def viewer(_ctx, str_val, _str_len):
        output.append(str_val)
        return 0

    fn(None, viewer)
    if not output:
        raise BotanException('View callback was not invoked')
    return output[0].decode('utf8')

def _ctype_str(s: str | None) -> bytes | None:
    if s is None:
        return None
    assert isinstance(s, str)
    if '\0' in s:
        raise ValueError('Botan C string arguments cannot contain embedded NUL bytes')
    return s.encode('utf-8')

def _ctype_to_str(s: bytes) -> str:
    return s.decode('utf-8')

def _ctype_bits(s: str | bytes) -> bytes:
    if isinstance(s, bytes):
        return s
    elif isinstance(s, str):
        return s.encode('utf-8')
    else:
        raise Exception("Internal error - unexpected type %s provided to _ctype_bits" % (type(s).__name__))

def _ctype_bufout(buf):
    return buf.raw

def _hex_encode(buf):
    return hexlify(buf).decode('ascii')



#
# Versioning
#
def version_major() -> int:
    """Returns the major number of the library version."""
    return int(_DLL.botan_version_major())

def version_minor() -> int:
    """Returns the minor number of the library version."""
    return int(_DLL.botan_version_minor())

def version_patch() -> int:
    """Returns the patch number of the library version."""
    return int(_DLL.botan_version_patch())

def ffi_api_version() -> int:
    """Returns the version of the FFI API provided by the library"""
    return int(_DLL.botan_ffi_api_version())

def version_string() -> str:
    """Returns a free form version string for the library"""
    return _DLL.botan_version_string().decode('ascii')

#
# Utilities
#
def const_time_compare(x: str | bytes, y: str | bytes) -> bool:
    """Compare two strings or byte vectors, returning True if they are equal.
    The comparison of the contents runs in constant time; unequal lengths are
    rejected immediately."""
    xbits = _ctype_bits(x)
    ybits = _ctype_bits(y)
    len_x = len(xbits)
    len_y = len(ybits)
    if len_x != len_y:
        return False
    rc = _DLL.botan_constant_time_compare(xbits, ybits, c_size_t(len_x))
    return rc == 0


MPILike = Union[str, "MPI", Any, None]  #: Alias for parameters that get turned into an MPI.

#
# TPM2
#

class TPM2Object:
    """Base class for objects that own a handle to a TPM 2.0 resource"""

    def __init__(self, obj: c_void_p, destroyer: Callable[[c_void_p], None]):
        """Take ownership of ``obj``, invoking ``destroyer`` on it during destruction"""
        self.__obj = obj
        self.__destroyer = destroyer

    def __del__(self):
        if hasattr(self, '__obj') and hasattr(self, '__destroyer'):
            self.__destroyer(self.__obj)

    def _handle(self):
        return self.__obj

class TPM2Context(TPM2Object):
    """TPM 2.0 Context object

    Create a TPM 2.0 context optionally with a TCTI name and configuration,
    separated by a colon, or as separate parameters.
    """

    def __init__(self, tcti_name_maybe_with_conf: str | None = None, tcti_conf: str | None = None):
        """Construct a TPM2Context object with optional TCTI name and configuration."""

        obj = c_void_p(0)
        if tcti_conf is not None:
            rc = _DLL.botan_tpm2_ctx_init_ex(byref(obj), _ctype_str(tcti_name_maybe_with_conf), _ctype_str(tcti_conf))
        else:
            rc = _DLL.botan_tpm2_ctx_init(byref(obj), _ctype_str(tcti_name_maybe_with_conf))
        if rc == -40: # 'Not Implemented'
            raise BotanException("TPM2 is not implemented in this build configuration", rc)
        self.rng_ = None
        super().__init__(obj, _DLL.botan_tpm2_ctx_destroy)

    @staticmethod
    def supports_botan_crypto_backend() -> bool:
        """Returns True if the given build supports the Botan-based crypto backend."""
        rc = _DLL.botan_tpm2_supports_crypto_backend()
        return rc == 1

    def enable_botan_crypto_backend(self, rng: RandomNumberGenerator):
        """Enables the Botan-based crypto backend.
        The passed rng MUST NOT be dependent on the TPM."""
        # By keeping a reference to the passed-in RNG object, we make sure
        # that the underlying object lives at least as long as this context.
        self.rng_ = rng
        _DLL.botan_tpm2_ctx_enable_crypto_backend(self._handle(), self.rng_._handle())

class TPM2Session(TPM2Object):
    """Basic TPM 2.0 Session object, typically users will instantiate a derived class."""

    def __init__(self, obj: c_void_p):
        """Take ownership of a TPM 2.0 session handle"""
        super().__init__(obj, _DLL.botan_tpm2_session_destroy)

    @staticmethod
    def session_bundle_(*args):
        """Transforms a session bundle passed by the downstream user into a 3-tuple of session handles.
        Users might pass a bare TPM2Session object or an iterable list of such objects."""
        if len(args) == 1:
            if isinstance(args[0], Iterable):
                args = list(args[0])
            elif args[0] is None:
                args = []

        if len(args) <= 3 and all(isinstance(s, TPM2Session) for s in args):
            sessions = list(args)
            while len(sessions) < 3:
                sessions.append(None)
            return (s._handle() if isinstance(s, TPM2Session) else None for s in sessions)
        else:
            raise BotanException("session bundle arguments must be 0 to 3 TPM2Session objects")


class TPM2UnauthenticatedSession(TPM2Session):
    """Session object that is not bound to any authentication credential.
    It provides basic parameter encryption between the application and the TPM."""

    def __init__(self, ctx: TPM2Context):
        """Create a new unauthenticated session within the given TPM 2.0 context"""
        obj = c_void_p(0)
        _DLL.botan_tpm2_unauthenticated_session_init(byref(obj), ctx._handle())
        super().__init__(obj)

class _CustomRngContext:
    def __init__(self, get_cb: Callable[[int], bytes], add_entropy_cb: Callable[[bytes], None] | None):
        if not callable(get_cb):
            raise BotanException("Custom RNG requires a callable get_callback= argument")
        if add_entropy_cb is not None and not callable(add_entropy_cb):
            raise BotanException("add_entropy_callback must be callable if provided")

        self.get_callback = get_cb
        self.add_entropy_callback = add_entropy_cb

    @staticmethod
    def _translate_exceptions(fn):
        def wrapper(*args, **kwargs):
            try:
                return fn(*args, **kwargs)
            except BotanException as e:
                return e.error_code()
            except Exception:
                return -100  # internal error
        return wrapper

    @staticmethod
    @_RNG_GET_CALLBACK
    @_translate_exceptions
    def _custom_get(ctx, out, out_len):
        py_ctx = cast(ctx, POINTER(py_object)).contents.value
        result = bytes(py_ctx.get_callback(out_len))
        if len(result) != out_len:
            return -1  # Invalid input
        memmove(out, result, out_len)
        return 0  # success

    @staticmethod
    @_RNG_ADD_ENTROPY_CALLBACK
    @_translate_exceptions
    def _custom_add_entropy(ctx, entropy, length):
        py_ctx = cast(ctx, POINTER(py_object)).contents.value
        if py_ctx.add_entropy_callback is not None:
            data = bytes(string_at(entropy, length))
            py_ctx.add_entropy_callback(data)
        return 0  # success

    @staticmethod
    @_RNG_DESTROY_CALLBACK
    @_translate_exceptions
    def _custom_destroy(ctx):
        pass

#
# RNG
#
class RandomNumberGenerator:
    """Previously ``rng``

    Type 'user' also allowed (userspace HMAC_DRBG seeded from system
    rng). The system RNG is very cheap to create, as just a single file
    handle or CSP handle is kept open, from first use until shutdown,
    no matter how many 'system' rng instances are created. Thus it is
    easy to use the RNG in a one-off way, with ``botan.RandomNumberGenerator().get(32)``.

    For some use cases it can be useful to provide a custom RNG implementation.
    Use 'custom' as the rng_type and provide the ``get_callback=`` and
    ``add_entropy_callback=`` arguments. The latter is optional.
    ``get_callback`` takes an integer and is expected to return a bytes object
    with the requested number of random bytes.
    ``add_entropy_callback`` takes a bytes object containing entropy bytes and
    is expected to add the given entropy to the RNG.

    When Botan is configured with TPM 2.0 support, also 'tpm2' is allowed
    to instantiate a TPM-backed RNG. Note that this requires passing
    additional named arguments ``tpm2_context=`` with a ``TPM2Context`` and
    (optionally) ``tpm2_sessions=`` with one or more ``TPM2Session`` objects.

    Constructs a RandomNumberGenerator of type rng_type.
    Available RNG types are:

    * 'system': Adapter to the operating system's RNG
    * 'user':   Software-PRNG that is auto-seeded by the system RNG
    * 'null':   Mock-RNG that fails if randomness is pulled from it
    * 'hwrng':  Adapter to an available hardware RNG (platform dependent)
    * 'tpm2':   Adapter to a TPM 2.0 RNG
                (needs additional named arguments tpm2_context= and, optionally, tpm2_sessions=)
    * 'custom': Adapter to user-defined callbacks
                (needs additional named arguments get_callback= and, optionally, add_entropy_callback=)
    """

    def __init__(self, rng_type: str = 'system', **kwargs):
        """Create a RandomNumberGenerator of the named type."""
        obj = kwargs.pop("_obj", None)
        if isinstance(obj, c_void_p):
            self.__obj = obj
            return

        self.__obj = c_void_p(0)
        if rng_type == 'custom':
            custom_rng_ctx = _CustomRngContext(kwargs.pop("get_callback", None), kwargs.pop("add_entropy_callback", None))
            if kwargs:
                raise BotanException("Unexpected arguments for custom RNG: %s" % (", ".join(kwargs.keys())))
            self._custom_rng_ctx_ref = py_object(custom_rng_ctx)
            _DLL.botan_rng_init_custom(
                byref(self.__obj),
                _ctype_str("Python Custom RNG"),
                cast(byref(self._custom_rng_ctx_ref), c_void_p),
                _CustomRngContext._custom_get,
                _CustomRngContext._custom_add_entropy,
                _CustomRngContext._custom_destroy,
            )
        elif rng_type == 'tpm2':
            ctx = kwargs.pop("tpm2_context", None)
            if not ctx or not isinstance(ctx, TPM2Context):
                raise BotanException("Cannot instantiate a TPM2-based RNG without a TPM2 context, pass tpm2_context= argument?")
            sessions = TPM2Session.session_bundle_(kwargs.pop("tpm2_sessions", None))
            if kwargs:
                raise BotanException("Unexpected arguments for TPM2 RNG: %s" % (", ".join(kwargs.keys())))
            _DLL.botan_tpm2_rng_init(byref(self.__obj), ctx._handle(), *sessions)
        else:
            if kwargs:
                raise BotanException("Unexpected arguments for RNG type %s: %s" % (rng_type, ", ".join(kwargs.keys())))
            _DLL.botan_rng_init(byref(self.__obj), _ctype_str(rng_type))

    def __del__(self):
        _DLL.botan_rng_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    def reseed(self, bits: int = 256):
        """Meaningless on system RNG, on userspace RNG causes a reseed/rekey"""
        _DLL.botan_rng_reseed(self.__obj, bits)

    def reseed_from_rng(self, source_rng: RandomNumberGenerator, bits: int = 256):
        """Take bits from the source RNG and use it to seed ``self``"""
        _DLL.botan_rng_reseed_from_rng(self.__obj, source_rng._handle(), bits)

    def add_entropy(self, seed: str | bytes):
        """Add some unpredictable seed data to the RNG"""
        seedbits = _ctype_bits(seed)
        _DLL.botan_rng_add_entropy(self.__obj, seedbits, len(seedbits))

    def get(self, length: int) -> bytes:
        """Return some bytes"""
        out = create_string_buffer(length)
        _DLL.botan_rng_get(self.__obj, out, c_size_t(length))
        return _ctype_bufout(out)

    def generate_with_input(self, length: int, additional_input: bytes) -> bytes:
        """Generate random bytes with additional input mixed in (for DRBGs)"""
        out = create_string_buffer(length)
        _DLL.botan_rng_generate_with_input(
            self.__obj, out, c_size_t(length),
            additional_input, c_size_t(len(additional_input)))
        return _ctype_bufout(out)

    @staticmethod
    def drbg(drbg_name: str, seed: bytes) -> RandomNumberGenerator:
        """Create a seeded DRBG (e.g. "HMAC_DRBG(SHA-256)")

        The seed should be the concatenation of entropy, nonce, and
        personalization string."""
        obj = c_void_p(0)
        _DLL.botan_rng_init_drbg(byref(obj), _ctype_str(drbg_name), seed, c_size_t(len(seed)))
        return RandomNumberGenerator(_obj=obj)

#
# Block cipher
#
class BlockCipher:
    """A raw block cipher, eg 'AES-128' or 'Threefish-512'

    This is a low level interface which does not provide any confidentiality
    on its own; most applications should use ``SymmetricCipher`` instead."""

    def __init__(self, algo: str | c_void_p):
        """Create a block cipher object for the named algorithm"""

        if isinstance(algo, c_void_p):
            self.__obj = algo
        else:
            flags = c_uint32(0) # always zero in this API version
            self.__obj = c_void_p(0)
            _DLL.botan_block_cipher_init(byref(self.__obj), _ctype_str(algo), flags)

        min_keylen = c_size_t(0)
        max_keylen = c_size_t(0)
        mod_keylen = c_size_t(0)
        _DLL.botan_block_cipher_get_keyspec(self.__obj, byref(min_keylen), byref(max_keylen), byref(mod_keylen))

        self.__min_keylen = min_keylen.value
        self.__max_keylen = max_keylen.value
        self.__mod_keylen = mod_keylen.value

        self.__block_size = _DLL.botan_block_cipher_block_size(self.__obj)

    def __del__(self):
        _DLL.botan_block_cipher_destroy(self.__obj)

    def set_key(self, key: bytes):
        """Set the key"""
        _DLL.botan_block_cipher_set_key(self.__obj, key, len(key))

    def encrypt(self, pt: bytes) -> Array[c_char]:
        """Encrypt the input, whose length must be a multiple of ``block_size()``"""
        if len(pt) % self.block_size() != 0:
            raise Exception("Invalid input must be multiple of block size")

        blocks = c_size_t(len(pt) // self.block_size())
        output = create_string_buffer(len(pt))
        _DLL.botan_block_cipher_encrypt_blocks(self.__obj, pt, output, blocks)
        return output

    def decrypt(self, ct: bytes) -> Array[c_char]:
        """Decrypt the input, whose length must be a multiple of ``block_size()``"""
        if len(ct) % self.block_size() != 0:
            raise Exception("Invalid input must be multiple of block size")

        blocks = c_size_t(len(ct) // self.block_size())
        output = create_string_buffer(len(ct))
        _DLL.botan_block_cipher_decrypt_blocks(self.__obj, ct, output, blocks)
        return output

    def algo_name(self) -> str:
        """Returns the name of this algorithm"""
        return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_block_cipher_name(self.__obj, b, bl))

    def clear(self):
        """Clear internal state including the key"""
        _DLL.botan_block_cipher_clear(self.__obj)

    def block_size(self) -> int:
        """Returns the block size in bytes"""
        return self.__block_size

    def minimum_keylength(self) -> int:
        """Returns the minimum key length in bytes"""
        return self.__min_keylen

    def maximum_keylength(self) -> int:
        """Returns the maximum key length in bytes"""
        return self.__max_keylen

    def keylength_modulo(self) -> int:
        """Returns the granularity of key lengths; any valid key length is a multiple of this value"""
        return self.__mod_keylen


#
# Hash function
#
class HashFunction:
    """Previously ``hash_function``

    The ``algo`` param is a string (eg 'SHA-1', 'SHA-384', 'BLAKE2b')"""

    def __init__(self, algo: str | c_void_p):
        """Create a hash function object for the named algorithm"""
        if isinstance(algo, c_void_p):
            self.__obj = algo
        else:
            flags = c_uint32(0) # always zero in this API version
            self.__obj = c_void_p(0)
            _DLL.botan_hash_init(byref(self.__obj), _ctype_str(algo), flags)

        self.__output_length = _call_fn_returning_sz(lambda length: _DLL.botan_hash_output_length(self.__obj, length))
        self.__block_size = _call_fn_returning_sz(lambda length: _DLL.botan_hash_block_size(self.__obj, length))

    def __del__(self):
        _DLL.botan_hash_destroy(self.__obj)

    def copy_state(self) -> HashFunction:
        """Returns an independent copy of this object, with the same state"""
        copy = c_void_p(0)
        _DLL.botan_hash_copy_state(byref(copy), self.__obj)
        return HashFunction(copy)

    def algo_name(self) -> str:
        """Returns the name of this algorithm"""
        return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_hash_name(self.__obj, b, bl))

    def clear(self):
        """Clear state"""
        _DLL.botan_hash_clear(self.__obj)

    def output_length(self) -> int:
        """Return output length in bytes"""
        return self.__output_length

    def block_size(self) -> int:
        """Return block size in bytes"""
        return self.__block_size

    def security_level(self) -> int:
        """Return the estimated security level, in bits, wrt collision resistance"""
        return _call_fn_returning_sz(lambda level: _DLL.botan_hash_security_level(self.__obj, level))

    def update(self, x: str | bytes):
        """Add some input"""
        bits = _ctype_bits(x)
        _DLL.botan_hash_update(self.__obj, bits, len(bits))

    def final(self) -> bytes:
        """Returns the hash of all input provided, resets for another message."""
        out = create_string_buffer(self.output_length())
        _DLL.botan_hash_final(self.__obj, out)
        return _ctype_bufout(out)

#
# eXtensible Output Functions
#
class XOF:
    """eXtensible Output Function (XOF). The ``algo`` param is a string (e.g 'SHAKE-256', 'Ascon-XOF128')"""

    def __init__(self, algo: str | c_void_p):
        """Create a XOF object for the named algorithm"""
        if isinstance(algo, c_void_p):
            self.__obj = algo
        else:
            flags = c_uint32(0) # always zero in this API version
            self.__obj = c_void_p(0)
            _DLL.botan_xof_init(byref(self.__obj), _ctype_str(algo), flags)

    def __del__(self):
        _DLL.botan_xof_destroy(self.__obj)

    def copy_state(self) -> XOF:
        """Returns an independent copy of this object, with the same state"""
        copy = c_void_p(0)
        _DLL.botan_xof_copy_state(byref(copy), self.__obj)
        return XOF(copy)

    def clear(self):
        """Clear state"""
        _DLL.botan_xof_clear(self.__obj)

    def update(self, x: str | bytes):
        """Add some input"""
        bits = _ctype_bits(x)
        _DLL.botan_xof_update(self.__obj, bits, len(bits))

    def output(self, length: int) -> bytes:
        """Returns ``length`` bytes of output from the XOF after all input was provided"""
        if length <= 0:
            return b''
        buf = create_string_buffer(length)
        _DLL.botan_xof_output(self.__obj, buf, c_size_t(length))
        return _ctype_bufout(buf)

    def accepts_input(self) -> bool:
        """Returns True if the XOF can accept more input, False if it is in output-only mode."""
        return _call_fn_returning_bool(lambda: _DLL.botan_xof_accepts_input(self.__obj))

    def algo_name(self) -> str:
        """Returns the name of this algorithm"""
        return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_xof_name(self.__obj, b, bl))

    def block_size(self) -> int:
        """Return block size in bytes"""
        return _call_fn_returning_sz(lambda length: _DLL.botan_xof_block_size(self.__obj, length))

#
# Message authentication codes
#
class MsgAuthCode:
    """Previously ``message_authentication_code``

    The constructor `algo` param is a string (eg 'HMAC(SHA-256)', 'Poly1305', 'CMAC(AES-256)')"""

    def __init__(self, algo: str):
        """Create a message authentication code object for the named algorithm"""
        flags = c_uint32(0) # always zero in this API version
        self.__obj = c_void_p(0)
        _DLL.botan_mac_init(byref(self.__obj), _ctype_str(algo), flags)

        min_keylen = c_size_t(0)
        max_keylen = c_size_t(0)
        mod_keylen = c_size_t(0)
        _DLL.botan_mac_get_keyspec(self.__obj, byref(min_keylen), byref(max_keylen), byref(mod_keylen))

        self.__min_keylen = min_keylen.value
        self.__max_keylen = max_keylen.value
        self.__mod_keylen = mod_keylen.value

        output_length = c_size_t(0)
        _DLL.botan_mac_output_length(self.__obj, byref(output_length))
        self.__output_length = output_length.value

    def __del__(self):
        _DLL.botan_mac_destroy(self.__obj)

    def clear(self):
        """Clear internal state including the key"""
        _DLL.botan_mac_clear(self.__obj)

    def algo_name(self) -> str:
        """Returns the name of this algorithm"""
        return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_mac_name(self.__obj, b, bl))

    def output_length(self) -> int:
        """Return the output length in bytes"""
        return self.__output_length

    def minimum_keylength(self) -> int:
        """Returns the minimum key length in bytes"""
        return self.__min_keylen

    def maximum_keylength(self) -> int:
        """Returns the maximum key length in bytes"""
        return self.__max_keylen

    def keylength_modulo(self) -> int:
        """Returns the granularity of key lengths; any valid key length is a multiple of this value"""
        return self.__mod_keylen

    def set_key(self, key: bytes):
        """Set the key"""
        _DLL.botan_mac_set_key(self.__obj, key, len(key))

    def set_nonce(self, nonce: bytes):
        """Set the nonce. Only a few MACs, such as GMAC, take a nonce"""
        _DLL.botan_mac_set_nonce(self.__obj, nonce, len(nonce))

    def update(self, x: str | bytes):
        """Add some input"""
        bits = _ctype_bits(x)
        _DLL.botan_mac_update(self.__obj, bits, len(bits))

    def final(self) -> bytes:
        """Returns the MAC of all input provided, resets for another message with the same key."""
        out = create_string_buffer(self.output_length())
        _DLL.botan_mac_final(self.__obj, out)
        return _ctype_bufout(out)

class SymmetricCipher:
    """Previously ``cipher``

    The algorithm is specified as a string (eg 'AES-128/GCM', 'Serpent/OCB(12)', 'Threefish-512/EAX').
    Set ``encrypt`` to False for decryption."""

    def __init__(self, algo: str, encrypt: bool = True):
        """Create a cipher object for the named algorithm, for encryption unless ``encrypt`` is False"""

        flags = 0 if encrypt else 1
        self.__obj = c_void_p(0)
        _DLL.botan_cipher_init(byref(self.__obj), _ctype_str(algo), flags)
        self._is_cbc = algo.find('/CBC') > 0
        self._is_encrypt = encrypt

    def __del__(self):
        _DLL.botan_cipher_destroy(self.__obj)

    def algo_name(self) -> str:
        """Returns the name of this algorithm"""
        return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_cipher_name(self.__obj, b, bl))

    def default_nonce_length(self) -> int:
        """Returns default nonce length"""
        length = c_size_t(0)
        _DLL.botan_cipher_get_default_nonce_length(self.__obj, byref(length))
        return length.value

    def update_granularity(self) -> int:
        """Returns update block size. Call to update() must provide input of exactly this many bytes"""
        length = c_size_t(0)
        _DLL.botan_cipher_get_update_granularity(self.__obj, byref(length))
        return length.value

    def ideal_update_granularity(self) -> int:
        """Returns a multiple of ``update_granularity()`` which is likely to provide the best performance"""
        length = c_size_t(0)
        _DLL.botan_cipher_get_ideal_update_granularity(self.__obj, byref(length))
        return length.value

    def key_length(self) -> tuple[int, int]:
        """Returns a tuple of the minimum and maximum key lengths, in bytes"""
        kmin = c_size_t(0)
        kmax = c_size_t(0)
        _DLL.botan_cipher_query_keylen(self.__obj, byref(kmin), byref(kmax))
        return kmin.value, kmax.value

    def minimum_keylength(self) -> int:
        """Returns the minimum key length in bytes"""
        length = c_size_t(0)
        _DLL.botan_cipher_get_keyspec(self.__obj, byref(length), None, None)
        return length.value

    def maximum_keylength(self) -> int:
        """Returns the maximum key length in bytes"""
        length = c_size_t(0)
        _DLL.botan_cipher_get_keyspec(self.__obj, None, byref(length), None)
        return length.value

    def tag_length(self) -> int:
        """Returns the tag length (0 for unauthenticated modes)"""
        length = c_size_t(0)
        _DLL.botan_cipher_get_tag_length(self.__obj, byref(length))
        return length.value

    def is_authenticated(self) -> bool:
        """Returns True if this is an AEAD mode"""
        rc = _DLL.botan_cipher_is_authenticated(self.__obj)
        return rc == 1

    def valid_nonce_length(self, nonce_len) -> bool:
        """Returns True if nonce_len is a valid nonce len for this mode"""
        rc = _DLL.botan_cipher_valid_nonce_length(self.__obj, nonce_len)
        return rc == 1

    def reset(self):
        """Reset the nonce and any state associated with the message processed so far,
        retaining the key. Equivalent to ``clear()`` followed by setting the same key again."""
        _DLL.botan_cipher_reset(self.__obj)

    def clear(self):
        """Resets all state"""
        _DLL.botan_cipher_clear(self.__obj)

    def set_key(self, key: bytes):
        """Set the key"""
        _DLL.botan_cipher_set_key(self.__obj, key, len(key))

    def set_assoc_data(self, ad: bytes):
        """Sets the associated data. Fails if this is not an AEAD mode"""
        _DLL.botan_cipher_set_associated_data(self.__obj, ad, len(ad))

    def start(self, nonce: bytes):
        """Start processing a message using nonce"""
        _DLL.botan_cipher_start(self.__obj, nonce, len(nonce))

    def _update(self, txt: str | bytes | None, final: bool):

        inp = txt if txt else ''
        bits = _ctype_bits(inp)
        inp_sz = c_size_t(len(bits))
        inp_consumed = c_size_t(0)
        extra_bytes = 0
        if final and self._is_encrypt:
            tag_len = self.tag_length()
            if tag_len > 0:
                # AEADs don't expand beyond the tag
                extra_bytes = tag_len
            elif self._is_cbc:
                # Hack: the largest block size currently supported
                extra_bytes = 64
        out = create_string_buffer(inp_sz.value + extra_bytes)
        out_sz = c_size_t(len(out))
        out_written = c_size_t(0)
        flags = c_uint32(1 if final else 0)

        _DLL.botan_cipher_update(self.__obj, flags,
                                 out, out_sz, byref(out_written),
                                 bits, inp_sz, byref(inp_consumed))

        # buffering not supported yet
        assert inp_consumed.value == inp_sz.value
        return out.raw[0:int(out_written.value)]

    def update(self, txt: str | bytes):
        """Consumes input text and returns output. Input text must be of update_granularity() length.
        Alternately, always call finish with the entire message, avoiding calls to update entirely"""
        return self._update(txt, False)

    def finish(self, txt: str | bytes | None = None):
        """Finish processing (with an optional final input). May throw if message authentication checks fail,
        in which case all plaintext previously processed must be discarded.
        You may call finish() with the entire message"""
        return self._update(txt, True)

def bcrypt(passwd: str, rng_obj: RandomNumberGenerator, work_factor=10):
    """
    Provided the password and an RNG object, returns a bcrypt string
    """
    out_len = c_size_t(64)
    out = create_string_buffer(out_len.value)
    flags = c_uint32(0)
    _DLL.botan_bcrypt_generate(out, byref(out_len), _ctype_str(passwd),
                               rng_obj._handle(), c_size_t(work_factor), flags)
    b = out.raw[0:int(out_len.value)-1]
    if b[-1] == '\x00':
        b = b[:-1]
    return _ctype_to_str(b)

def check_bcrypt(passwd: str, passwd_hash: str):
    """ Check a bcrypt hash against the provided password, returning True iff the password matches."""
    rc = _DLL.botan_bcrypt_is_valid(_ctype_str(passwd), _ctype_str(passwd_hash))
    return rc == 0

#
# PBKDF
#
def pbkdf(algo: str, password: str, out_len: int, iterations: int = 100000, salt: bytes | None = None) -> tuple[bytes, int, bytes]:
    """Runs a PBKDF2 algo specified as a string (eg 'PBKDF2(SHA-256)',
    'PBKDF2(CMAC(Blowfish))').  Runs with specified iterations, with
    meaning depending on the algorithm.  The salt can be provided or
    otherwise is randomly chosen. In any case it is returned from the
    call.

    Returns out_len bytes of output (or potentially less depending on
    the algorithm and the size of the request).

    Returns tuple of salt, iterations, and psk"""
    if salt is None:
        salt = RandomNumberGenerator().get(12)

    out_buf = create_string_buffer(out_len)

    passbits = _ctype_bits(password)
    _DLL.botan_pwdhash(_ctype_str(algo), iterations, 0, 0,
                       out_buf, out_len,
                       passbits, len(passbits),
                       salt, len(salt))
    return (salt, iterations, out_buf.raw)

def pbkdf_timed(algo: str, password: str, out_len: int, ms_to_run: int = 300, salt: bytes | None = None) -> tuple[bytes, int, bytes]:
    """Runs for as many iterations as needed to consumed ms_to_run
    milliseconds on whatever we're running on. Returns tuple of salt,
    iterations, and psk"""
    if salt is None:
        salt = RandomNumberGenerator().get(12)

    out_buf = create_string_buffer(out_len)
    iterations = c_size_t(0)

    passbits = _ctype_bits(password)
    _DLL.botan_pwdhash_timed(_ctype_str(algo), c_uint32(ms_to_run),
                             byref(iterations), None, None,
                             out_buf, out_len,
                             passbits, len(passbits),
                             salt, len(salt))
    return (salt, iterations.value, out_buf.raw)

#
# Scrypt
#
def scrypt(out_len: int, password: str, salt: str | bytes, n: int = 1024, r: int = 8, p: int = 8) -> bytes:
    """Runs Scrypt key derivation function over the specified password
    and salt using Scrypt parameters N, r, p."""
    out_buf = create_string_buffer(out_len)
    passbits = _ctype_bits(password)
    saltbits = _ctype_bits(salt)

    _DLL.botan_pwdhash(_ctype_str("Scrypt"), n, r, p,
                       out_buf, out_len,
                       passbits, len(passbits),
                       saltbits, len(saltbits))

    return out_buf.raw

#
# Argon2
#
def argon2(variant: str, out_len: int, password: str, salt: str | bytes, m: int = 256, t: int = 1, p: int = 1) -> bytes:
    """Runs the Argon2 password hashing function, returning ``out_len`` bytes

    The ``variant`` should be "Argon2i", "Argon2d", or "Argon2id".
    ``m`` specifies the memory used during processing, in kibibytes,
    ``t`` the number of passes, and ``p`` the parallelism."""
    out_buf = create_string_buffer(out_len)
    passbits = _ctype_bits(password)
    saltbits = _ctype_bits(salt)

    _DLL.botan_pwdhash(_ctype_str(variant), m, t, p,
                       out_buf, out_len,
                       passbits, len(passbits),
                       saltbits, len(saltbits))

    return out_buf.raw

#
# KDF
#
def kdf(algo: str, secret: bytes, out_len: int, salt: bytes, label: bytes) -> bytes:
    """Performs a key derivation function (such as "HKDF(SHA-384)") over the provided secret
    and salt values. Returns a value of the specified length."""
    out_buf = create_string_buffer(out_len)
    out_sz = c_size_t(out_len)
    _DLL.botan_kdf(_ctype_str(algo), out_buf, out_sz,
                   secret, len(secret),
                   salt, len(salt),
                   label, len(label))
    return out_buf.raw[0:int(out_sz.value)]

#
# Public key
#
class PublicKey: # pylint: disable=invalid-name
    """Previously ``public_key``"""

    def __init__(self, obj: c_void_p | None = None):
        """Create a public key object wrapping the given FFI handle, or an empty one.
        Applications should use one of the ``load`` methods instead."""
        if not obj:
            obj = c_void_p(0)
        self.__obj = obj

    @classmethod
    def load(cls, val: str | bytes) -> PublicKey:
        """Load a public key. The value should be a PEM or DER blob."""
        pub = PublicKey()
        bits = _ctype_bits(val)
        _DLL.botan_pubkey_load(byref(pub._handle()), bits, len(bits))
        return pub

    @classmethod
    def load_rsa(cls, n: MPILike, e: MPILike) -> PublicKey:
        """Load an RSA public key giving the modulus and public exponent as integers."""
        pub = PublicKey()
        n = MPI(n)
        e = MPI(e)
        _DLL.botan_pubkey_load_rsa(byref(pub._handle()), n._handle(), e._handle())
        return pub

    @classmethod
    def load_dsa(cls, p: MPILike, q: MPILike, g: MPILike, y: MPILike) -> PublicKey:
        """Load a DSA public key giving the parameters and public value as integers."""
        pub = PublicKey()
        p = MPI(p)
        q = MPI(q)
        g = MPI(g)
        y = MPI(y)
        _DLL.botan_pubkey_load_dsa(byref(pub._handle()), p._handle(), q._handle(), g._handle(), y._handle())
        return pub

    @classmethod
    def load_dh(cls, p: MPILike, g: MPILike, y: MPILike) -> PublicKey:
        """Load a Diffie-Hellman public key giving the parameters and public value as integers."""
        pub = PublicKey()
        p = MPI(p)
        g = MPI(g)
        y = MPI(y)
        _DLL.botan_pubkey_load_dh(byref(pub._handle()), p._handle(), g._handle(), y._handle())
        return pub

    @classmethod
    def load_elgamal(cls, p: MPILike, q: MPILike, g: MPILike, y: MPILike) -> PublicKey:
        """Load an ElGamal public key giving the parameters and public value as integers."""
        pub = PublicKey()
        p = MPI(p)
        q = MPI(q)
        g = MPI(g)
        y = MPI(y)
        _DLL.botan_pubkey_load_elgamal(byref(pub._handle()), p._handle(), q._handle(), g._handle(), y._handle())
        return pub

    @classmethod
    def load_ecdsa(cls, curve: str, pub_x: MPILike, pub_y: MPILike) -> PublicKey:
        """Load an ECDSA public key giving the curve as a string (like "secp256r1") and the public point
        as a pair of integers giving the affine coordinates."""
        pub = PublicKey()
        pub_x = MPI(pub_x)
        pub_y = MPI(pub_y)
        _DLL.botan_pubkey_load_ecdsa(byref(pub._handle()), pub_x._handle(), pub_y._handle(), _ctype_str(curve))
        return pub

    @classmethod
    def load_ecdsa_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey:
        """Load an ECDSA public key giving the curve as a string (like "secp256r1")
        and the public point in SEC1 format."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_ecdsa_sec1(byref(pub._handle()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve))
        return pub

    @classmethod
    def load_ecdh(cls, curve: str, pub_x: MPILike, pub_y: MPILike) -> PublicKey:
        """Load an ECDH public key giving the curve as a string (like "secp256r1") and the public point
        as a pair of integers giving the affine coordinates."""
        pub = PublicKey()
        pub_x = MPI(pub_x)
        pub_y = MPI(pub_y)
        _DLL.botan_pubkey_load_ecdh(byref(pub._handle()), pub_x._handle(), pub_y._handle(), _ctype_str(curve))
        return pub

    @classmethod
    def load_ecdh_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey:
        """Load an ECDH public key giving the curve as a string (like "secp256r1")
        and the public point in SEC1 format."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_ecdh_sec1(byref(pub._handle()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve))
        return pub

    @classmethod
    def load_sm2(cls, curve: str, pub_x: MPILike, pub_y: MPILike) -> PublicKey:
        """Load a SM2 public key giving the curve as a string (like "sm2p256v1") and the public point
        as a pair of integers giving the affine coordinates."""
        pub = PublicKey()
        pub_x = MPI(pub_x)
        pub_y = MPI(pub_y)
        _DLL.botan_pubkey_load_sm2(byref(pub._handle()), pub_x._handle(), pub_y._handle(), _ctype_str(curve))
        return pub

    @classmethod
    def load_sm2_sec1(cls, curve: str, sec1_encoding: str | bytes) -> PublicKey:
        """Load a SM2 public key giving the curve as a string (like "sm2p256v1")
        and the public point in SEC1 format."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_sm2_sec1(byref(pub._handle()), _ctype_bits(sec1_encoding), len(sec1_encoding), _ctype_str(curve))
        return pub

    @classmethod
    def load_kyber(cls, key: bytes) -> PublicKey:
        """Load a Kyber public key from the raw encoding of the public key"""
        pub = PublicKey()
        _DLL.botan_pubkey_load_kyber(byref(pub._handle()), key, len(key))
        return pub

    @classmethod
    def load_ml_kem(cls, mlkem_mode: str, key: bytes) -> PublicKey:
        """Load an ML-KEM public key giving the mode as a string (like "ML-KEM-512")
        and the raw encoding of the public key."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_ml_kem(byref(pub._handle()), key, len(key), _ctype_str(mlkem_mode))
        return pub

    @classmethod
    def load_ml_dsa(cls, mldsa_mode: str, key: bytes) -> PublicKey:
        """Load an ML-DSA public key giving the mode as a string (like "ML-DSA-4x4")
        and the raw encoding of the public key."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_ml_dsa(byref(pub._handle()), key, len(key), _ctype_str(mldsa_mode))
        return pub

    @classmethod
    def load_slh_dsa(cls, slhdsa_mode: str, key: bytes) -> PublicKey:
        """Load an SLH-DSA public key giving the mode as a string (like "SLH-DSA-SHAKE-128f")
        and the raw encoding of the public key."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_slh_dsa(byref(pub._handle()), key, len(key), _ctype_str(slhdsa_mode))
        return pub

    @classmethod
    def load_frodokem(cls, frodo_mode: str, key: bytes) -> PublicKey:
        """Load a FrodoKEM public key giving the mode as a string (like "FrodoKEM-640-SHAKE")
        and the raw encoding of the public key."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_frodokem(byref(pub._handle()), key, len(key), _ctype_str(frodo_mode))
        return pub

    @classmethod
    def load_classic_mceliece(cls, cmce_mode: str, key: bytes) -> PublicKey:
        """Load a Classic McEliece public key giving the mode as a string (like "348864f")
        and the raw encoding of the public key."""
        pub = PublicKey()
        _DLL.botan_pubkey_load_classic_mceliece(byref(pub._handle()), key, len(key), _ctype_str(cmce_mode))
        return pub

    @classmethod
    def load_x25519(cls, key: bytes) -> PublicKey:
        """Return a public X25519 key from 32 raw bytes"""
        if len(key) != 32:
            raise BotanException("Invalid input length to load_x25519")
        pub = PublicKey()
        _DLL.botan_pubkey_load_x25519(byref(pub._handle()), key)
        return pub

    @classmethod
    def load_x448(cls, key: bytes) -> PublicKey:
        """Return a public X448 key from 56 raw bytes"""
        if len(key) != 56:
            raise BotanException("Invalid input length to load_x448")
        pub = PublicKey()
        _DLL.botan_pubkey_load_x448(byref(pub._handle()), key)
        return pub

    def __del__(self):
        _DLL.botan_pubkey_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    def check_key(self, rng_obj: RandomNumberGenerator, strong: bool = True) -> bool:
        """Test the key for consistency. If ``strong`` is ``True`` then more expensive tests are performed."""
        flags = 1 if strong else 0
        rc = _DLL.botan_pubkey_check_key(self.__obj, rng_obj._handle(), flags)
        return rc == 0

    def estimated_strength(self) -> int:
        """Returns the estimated strength of this key against known attacks
        (NFS, Pollard's rho, etc)"""
        r = c_size_t(0)
        _DLL.botan_pubkey_estimated_strength(self.__obj, byref(r))
        return r.value

    def algo_name(self) -> str:
        """Returns the algorithm name"""
        return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_pubkey_algo_name(self.__obj, b, bl))

    def export(self, pem: bool = False) -> str | bytes:
        """Exports the public key using the usual X.509 SPKI representation.
        If ``pem`` is True, the result is a PEM encoded string. Otherwise
        it is a binary DER value."""
        if pem:
            return self.to_pem()
        else:
            return self.to_der()

    def to_der(self) -> bytes:
        """Like ``self.export(False)``"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_der(self.__obj, vc, vfn))

    def to_pem(self) -> str:
        """Like ``self.export(True)``"""
        return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_pubkey_view_pem(self.__obj, vc, vfn))

    def to_raw(self) -> bytes:
        """Exports the key in its canonical raw encoding.
        This might not be available for all key types and raise an exception in that case."""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_raw(self.__obj, vc, vfn))

    def view_kyber_raw_key(self) -> bytes:
        """Deprecated: use to_raw() instead"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_kyber_raw_key(self.__obj, vc, vfn))

    def fingerprint(self, hash_algorithm: str = 'SHA-256') -> str:
        """Returns a hash of the public key"""
        n = HashFunction(hash_algorithm).output_length()
        buf = create_string_buffer(n)
        buf_len = c_size_t(n)

        _DLL.botan_pubkey_fingerprint(self.__obj, _ctype_str(hash_algorithm), buf, byref(buf_len))
        return _hex_encode(buf[0:int(buf_len.value)])

    def get_field(self, field_name: str) -> int:
        """Return an integer field related to the public key. The valid field names
        vary depending on the algorithm. For example RSA public modulus can be
        extracted with ``rsa_key.get_field("n")``."""
        v = MPI()
        _DLL.botan_pubkey_get_field(v._handle(), self.__obj, _ctype_str(field_name))
        return int(v)

    def object_identifier(self) -> OID:
        """Returns the associated OID"""
        oid = OID()
        _DLL.botan_pubkey_oid(byref(oid._handle()), self.__obj)
        return oid

    def get_public_point(self) -> bytes:
        """Returns the SEC1 uncompressed encoding of the public point, if this is an EC key"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_pubkey_view_ec_public_point(self.__obj, vc, vfn))

    def used_explicit_encoding(self) -> bool:
        """Returns True if this key was decoded from a structure which encoded the
        elliptic curve using explicit parameters rather than a named curve.
        Raises an exception if the key is not an EC key."""
        rc = _DLL.botan_pubkey_ecc_key_used_explicit_encoding(self.__obj)
        if rc == -32:
            raise BotanException("Only ECC keys have a notion of explicit encoding")
        return rc == 1

    def get_group(self) -> ECGroup:
        """Return the group associated with the key if the key is an EC key.
        Raises an exception if the key is not an EC key."""
        group = ECGroup()
        _DLL.botan_ec_pubkey_get_group(self.__obj, byref(group._handle()))
        return group


#
# Private Key
#
class PrivateKey:
    """Previously ``private_key``"""

    def __init__(self, obj: c_void_p | None = None):
        """Create a private key object wrapping the given FFI handle, or an empty one.
        Applications should use ``create`` or one of the ``load`` methods instead."""
        if not obj:
            obj = c_void_p(0)
        self.__obj = obj

    @classmethod
    def load(cls, val: str | bytes, passphrase: str | None = None) -> PrivateKey:
        """Return a private key (DER or PEM formats accepted)"""
        priv = PrivateKey()
        rng_obj = c_void_p(0) # unused in recent versions
        bits = _ctype_bits(val)
        passwd = None if passphrase is None else _ctype_str(passphrase)
        _DLL.botan_privkey_load(byref(priv._handle()), rng_obj, bits, len(bits), passwd)
        return priv

    @classmethod
    def create(cls, algo: str, params: str | int | tuple[int, int], rng_obj: RandomNumberGenerator) -> PrivateKey:
        """Creates a new private key. The parameter type/value depends on
        the algorithm. For "rsa" is is the size of the key in bits.
        For "ecdsa" and "ecdh" it is a group name (for instance
        "secp256r1"). For "ecdh" there is also a special case for groups
        "curve25519" and "x448" (which are actually completely distinct key types
        with a non-standard encoding)."""
        if algo == 'rsa':
            algo = 'RSA'
            params = "%d" % (params)
        elif algo == 'ecdsa':
            algo = 'ECDSA'
        elif algo in ['ecdh', 'ECDH']:
            if params in ['x25519', 'curve25519']:
                algo = 'X25519'
                params = ''
            elif params == 'x448':
                algo = 'X448'
                params = ''
            else:
                algo = 'ECDH'
        elif algo in ['mce', 'mceliece']:
            # TODO(Botan4) remove this case
            algo = 'McEliece'
            params = "%d,%d" % (params[0], params[1])

        priv = PrivateKey()
        _DLL.botan_privkey_create(byref(priv._handle()), _ctype_str(algo), _ctype_str(params), rng_obj._handle())
        return priv

    @classmethod
    def create_ec(cls, algo: str, ec_group: ECGroup, rng_obj: RandomNumberGenerator) -> PrivateKey:
        """Creates a new ec private key."""
        obj = c_void_p(0)
        _DLL.botan_ec_privkey_create(byref(obj), _ctype_str(algo), ec_group._handle(), rng_obj._handle())
        return PrivateKey(obj)

    @classmethod
    def load_rsa(cls, p: MPILike, q: MPILike, e: MPILike) -> PrivateKey:
        """Return a private RSA key"""
        priv = PrivateKey()
        p = MPI(p)
        q = MPI(q)
        e = MPI(e)
        _DLL.botan_privkey_load_rsa(byref(priv._handle()), p._handle(), q._handle(), e._handle())
        return priv

    @classmethod
    def load_dsa(cls, p: MPILike, q: MPILike, g: MPILike, x: MPILike) -> PrivateKey:
        """Return a private DSA key"""
        priv = PrivateKey()
        p = MPI(p)
        q = MPI(q)
        g = MPI(g)
        x = MPI(x)
        _DLL.botan_privkey_load_dsa(byref(priv._handle()), p._handle(), q._handle(), g._handle(), x._handle())
        return priv

    @classmethod
    def load_dh(cls, p: MPILike, g: MPILike, x: MPILike) -> PrivateKey:
        """Return a private DH key"""
        priv = PrivateKey()
        p = MPI(p)
        g = MPI(g)
        x = MPI(x)
        _DLL.botan_privkey_load_dh(byref(priv._handle()), p._handle(), g._handle(), x._handle())
        return priv

    @classmethod
    def load_elgamal(cls, p: MPILike, q: MPILike, g: MPILike, x: MPILike) -> PrivateKey:
        """Return a private ElGamal key"""
        priv = PrivateKey()
        p = MPI(p)
        q = MPI(q)
        g = MPI(g)
        x = MPI(x)
        _DLL.botan_privkey_load_elgamal(byref(priv._handle()), p._handle(), q._handle(), g._handle(), x._handle())
        return priv

    @classmethod
    def load_ecdsa(cls, curve: str, x: MPILike) -> PrivateKey:
        """Return a private ECDSA key"""
        priv = PrivateKey()
        x = MPI(x)
        _DLL.botan_privkey_load_ecdsa(byref(priv._handle()), x._handle(), _ctype_str(curve))
        return priv

    @classmethod
    def load_ecdh(cls, curve: str, x: MPILike) -> PrivateKey:
        """Return a private ECDH key"""
        priv = PrivateKey()
        x = MPI(x)
        _DLL.botan_privkey_load_ecdh(byref(priv._handle()), x._handle(), _ctype_str(curve))
        return priv

    @classmethod
    def load_sm2(cls, curve: str, x: MPILike) -> PrivateKey:
        """Return a private SM2 key"""
        priv = PrivateKey()
        x = MPI(x)
        _DLL.botan_privkey_load_sm2(byref(priv._handle()), x._handle(), _ctype_str(curve))
        return priv

    @classmethod
    def load_kyber(cls, key: bytes) -> PrivateKey:
        """Return a private Kyber key from the raw encoding of the private key"""
        priv = PrivateKey()
        _DLL.botan_privkey_load_kyber(byref(priv._handle()), key, len(key))
        return priv

    @classmethod
    def load_ml_kem(cls, mlkem_mode: str, key: bytes) -> PrivateKey:
        """Return a private ML-KEM key"""
        priv = PrivateKey()
        _DLL.botan_privkey_load_ml_kem(byref(priv._handle()), key, len(key), _ctype_str(mlkem_mode))
        return priv

    @classmethod
    def load_ml_dsa(cls, mldsa_mode: str, key: bytes) -> PrivateKey:
        """Return a private ML-DSA key"""
        priv = PrivateKey()
        _DLL.botan_privkey_load_ml_dsa(byref(priv._handle()), key, len(key), _ctype_str(mldsa_mode))
        return priv

    @classmethod
    def load_slh_dsa(cls, slh_dsa: str, key: bytes) -> PrivateKey:
        """Return a private SLH-DSA key"""
        priv = PrivateKey()
        _DLL.botan_privkey_load_slh_dsa(byref(priv._handle()), key, len(key), _ctype_str(slh_dsa))
        return priv

    @classmethod
    def load_frodokem(cls, frodo_mode: str, key: bytes) -> PrivateKey:
        """Return a private FrodoKEM key giving the mode as a string (like "FrodoKEM-640-SHAKE")
        and the raw encoding of the private key."""
        priv = PrivateKey()
        _DLL.botan_privkey_load_frodokem(byref(priv._handle()), key, len(key), _ctype_str(frodo_mode))
        return priv

    @classmethod
    def load_classic_mceliece(cls, cmce_mode: str, key: bytes) -> PrivateKey:
        """Return a private Classic McEliece key giving the mode as a string (like "348864f")
        and the raw encoding of the private key."""
        priv = PrivateKey()
        _DLL.botan_privkey_load_classic_mceliece(byref(priv._handle()), key, len(key), _ctype_str(cmce_mode))
        return priv

    @classmethod
    def load_x25519(cls, key: bytes) -> PrivateKey:
        """Return a private X25519 key from 32 raw bytes"""
        if len(key) != 32:
            raise BotanException("Invalid input length to load_x25519")
        priv = PrivateKey()
        _DLL.botan_privkey_load_x25519(byref(priv._handle()), key)
        return priv

    @classmethod
    def load_x448(cls, key: bytes) -> PrivateKey:
        """Return a private X448 key from 56 raw bytes"""
        if len(key) != 56:
            raise BotanException("Invalid input length to load_x448")
        priv = PrivateKey()
        _DLL.botan_privkey_load_x448(byref(priv._handle()), key)
        return priv

    def __del__(self):
        _DLL.botan_privkey_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    def check_key(self, rng_obj: RandomNumberGenerator, strong: bool = True) -> bool:
        """Test the key for consistency. If ``strong`` is ``True`` then more expensive tests are performed."""
        flags = 1 if strong else 0
        rc = _DLL.botan_privkey_check_key(self.__obj, rng_obj._handle(), flags)
        return rc == 0

    def algo_name(self) -> str:
        """Returns the algorithm name"""
        return _call_fn_returning_str(32, lambda b, bl: _DLL.botan_privkey_algo_name(self.__obj, b, bl))

    def get_public_key(self) -> PublicKey:
        """Return a public_key object"""
        pub = PublicKey()
        _DLL.botan_privkey_export_pubkey(byref(pub._handle()), self.__obj)
        return pub

    def to_der(self) -> bytes:
        """Return the DER encoded private key (unencrypted). Like ``self.export(False)``"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_der(self.__obj, vc, vfn))

    def to_pem(self) -> str:
        """Return the PEM encoded private key (unencrypted). Like ``self.export(True)``"""
        return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_privkey_view_pem(self.__obj, vc, vfn))

    def to_raw(self) -> bytes:
        """Exports the key in its canonical raw encoding.
        This might not be available for all key types and raise an exception in that case."""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_raw(self.__obj, vc, vfn))

    def view_kyber_raw_key(self) -> bytes:
        """Deprecated: use to_raw() instead"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_privkey_view_kyber_raw_key(self.__obj, vc, vfn))

    def export(self, pem: bool = False) -> str | bytes:
        """Exports the private key in PKCS8 format. If ``pem`` is True, the
        result is a PEM encoded string. Otherwise it is a binary DER
        value. The key will not be encrypted."""
        if pem:
            return self.to_pem()
        else:
            return self.to_der()

    def export_encrypted(self, passphrase: str, rng: RandomNumberGenerator, pem: bool = False, msec: int = 300, cipher: str | None = None, pbkdf: str | None = None): # pylint: disable=redefined-outer-name
        """Exports the private key in PKCS8 format, encrypted using the
        provided passphrase. If ``pem`` is True, the result is a PEM
        encoded string. Otherwise it is a binary DER value."""
        if pem:
            return _call_fn_viewing_str(
                lambda vc, vfn: _DLL.botan_privkey_view_encrypted_pem_timed(
                    self.__obj, rng._handle(), _ctype_str(passphrase),
                    _ctype_str(cipher), _ctype_str(pbkdf), c_size_t(msec), vc, vfn))
        else:
            return _call_fn_viewing_vec(
                lambda vc, vfn: _DLL.botan_privkey_view_encrypted_der_timed(
                    self.__obj, rng._handle(), _ctype_str(passphrase),
                    _ctype_str(cipher), _ctype_str(pbkdf), c_size_t(msec), vc, vfn))

    def get_field(self, field_name: str) -> int:
        """Return an integer field related to the public key. The valid field names
        vary depending on the algorithm. For example first RSA secret prime can be
        extracted with ``rsa_key.get_field("p")``. This function can also be
        used to extract the public parameters."""
        v = MPI()
        _DLL.botan_privkey_get_field(v._handle(), self.__obj, _ctype_str(field_name))
        return int(v)

    def object_identifier(self) -> OID:
        """Return the associated OID"""
        oid = OID()
        _DLL.botan_privkey_oid(byref(oid._handle()), self.__obj)
        return oid

    def stateful_operation(self) -> bool:
        """Return whether the key is stateful or not."""
        r = c_int(0)
        _DLL.botan_privkey_stateful_operation(self.__obj, byref(r))
        if r.value == 0:
            return False
        return True

    def remaining_operations(self) -> int:
        """If the key is stateful, return the number of remaining operations.
        Raises an exception if the key is not stateful."""
        r = c_uint64(0)
        _DLL.botan_privkey_remaining_operations(self.__obj, byref(r))
        return r.value

    def get_private_key(self) -> ECScalar:
        """Return the private value if the key is an EC key."""
        scalar = ECScalar()
        _DLL.botan_ec_privkey_get_private_key(self.__obj, byref(scalar._handle()))
        return scalar

    def get_group(self) -> ECGroup:
        """Return the group associated with the key if the key is an EC key.
        Raises an exception if the key is not an EC key."""
        group = ECGroup()
        _DLL.botan_ec_privkey_get_group(self.__obj, byref(group._handle()))
        return group


class PKEncrypt:
    """Previously ``pk_op_encrypt``"""

    def __init__(self, key: PublicKey, padding: str):
        """Create an encryption operation using ``key`` and the named padding (eg "OAEP(SHA-256)")"""
        self.__obj = c_void_p(0)
        flags = c_uint32(0) # always zero in this ABI
        _DLL.botan_pk_op_encrypt_create(byref(self.__obj), key._handle(), _ctype_str(padding), flags)

    def __del__(self):
        _DLL.botan_pk_op_encrypt_destroy(self.__obj)

    def encrypt(self, msg: bytes, rng_obj: RandomNumberGenerator) -> bytes:
        """Encrypt a message, returning the ciphertext"""
        outbuf_sz = c_size_t(0)
        _DLL.botan_pk_op_encrypt_output_length(self.__obj, len(msg), byref(outbuf_sz))
        outbuf = create_string_buffer(outbuf_sz.value)
        _DLL.botan_pk_op_encrypt(self.__obj, rng_obj._handle(), outbuf, byref(outbuf_sz), msg, len(msg))
        return outbuf.raw[0:int(outbuf_sz.value)]


class PKDecrypt:
    """Previously ``pk_op_decrypt``"""

    def __init__(self, key: PrivateKey, padding: str):
        """Create a decryption operation using ``key`` and the named padding (eg "OAEP(SHA-256)")"""
        self.__obj = c_void_p(0)
        flags = c_uint32(0) # always zero in this ABI
        _DLL.botan_pk_op_decrypt_create(byref(self.__obj), key._handle(), _ctype_str(padding), flags)

    def __del__(self):
        _DLL.botan_pk_op_decrypt_destroy(self.__obj)

    def decrypt(self, msg: bytes) -> bytes:
        """Decrypt a ciphertext, returning the plaintext"""
        outbuf_sz = c_size_t(0)
        _DLL.botan_pk_op_decrypt_output_length(self.__obj, len(msg), byref(outbuf_sz))
        outbuf = create_string_buffer(outbuf_sz.value)
        bits = _ctype_bits(msg)
        _DLL.botan_pk_op_decrypt(self.__obj, outbuf, byref(outbuf_sz), bits, len(bits))
        return outbuf.raw[0:int(outbuf_sz.value)]

class PKSign: # pylint: disable=invalid-name
    """Previously ``pk_op_sign``"""

    def __init__(self, key: PrivateKey, padding: str, der: bool = False):
        """Create a signature operation using ``key`` and the named padding, eg "PSS(SHA-256)".
        If ``der`` is True then the signature is DER encoded."""
        self.__obj = c_void_p(0)
        flags = c_uint32(1) if der else c_uint32(0)
        _DLL.botan_pk_op_sign_create(byref(self.__obj), key._handle(), _ctype_str(padding), flags)

    def __del__(self):
        _DLL.botan_pk_op_sign_destroy(self.__obj)

    def update(self, msg: str | bytes):
        """Add more data to be signed"""
        bits = _ctype_bits(msg)
        _DLL.botan_pk_op_sign_update(self.__obj, bits, len(bits))

    def finish(self, rng_obj: RandomNumberGenerator) -> bytes:
        """Returns the signature of the message provided so far, and resets for another message"""
        outbuf_sz = c_size_t(0)
        _DLL.botan_pk_op_sign_output_length(self.__obj, byref(outbuf_sz))
        outbuf = create_string_buffer(outbuf_sz.value)
        _DLL.botan_pk_op_sign_finish(self.__obj, rng_obj._handle(), outbuf, byref(outbuf_sz))
        return outbuf.raw[0:int(outbuf_sz.value)]

class PKVerify:
    """Previously ``pk_op_verify``"""

    def __init__(self, key: PublicKey, padding: str, der: bool = False):
        """Create a verification operation using ``key`` and the named padding, eg "PSS(SHA-256)".
        If ``der`` is True then the signature is expected to be DER encoded."""
        self.__obj = c_void_p(0)
        flags = c_uint32(1) if der else c_uint32(0)
        _DLL.botan_pk_op_verify_create(byref(self.__obj), key._handle(), _ctype_str(padding), flags)

    def __del__(self):
        _DLL.botan_pk_op_verify_destroy(self.__obj)

    def update(self, msg: str | bytes):
        """Add more data to be verified"""
        bits = _ctype_bits(msg)
        _DLL.botan_pk_op_verify_update(self.__obj, bits, len(bits))

    def check_signature(self, signature: str | bytes) -> bool:
        """Returns True if the signature is valid for the message provided so far,
        and resets for another message"""
        bits = _ctype_bits(signature)
        rc = _DLL.botan_pk_op_verify_finish(self.__obj, bits, len(bits))
        if rc == 0:
            return True
        return False

class PKKeyAgreement:
    """Previously ``pk_op_key_agreement``"""

    def __init__(self, key: PrivateKey, kdf_name: str):
        """Create a key agreement operation using ``key`` and the named KDF,
        eg "KDF2(SHA-256)", or "Raw" to return the agreed value directly."""
        self.__obj = c_void_p(0)
        flags = c_uint32(0) # always zero in this ABI
        _DLL.botan_pk_op_key_agreement_create(byref(self.__obj), key._handle(), _ctype_str(kdf_name), flags)

        self.m_public_value = _call_fn_viewing_vec(
            lambda vc, vfn: _DLL.botan_pk_op_key_agreement_view_public(key._handle(), vc, vfn))

    def __del__(self):
        _DLL.botan_pk_op_key_agreement_destroy(self.__obj)

    def public_value(self) -> bytes:
        """Returns the public value to be passed to the other party"""
        return self.m_public_value

    def underlying_output_length(self) -> int:
        """Returns the length of the agreed value before the KDF is applied"""
        out_len = c_size_t(0)
        _DLL.botan_pk_op_key_agreement_size(self.__obj, byref(out_len))
        return out_len.value

    def agree(self, other: bytes, key_len: int, salt: bytes) -> bytes:
        """Returns a key derived by the KDF."""
        if key_len == 0:
            key_len = self.underlying_output_length()
        return _call_fn_returning_vec(key_len, lambda b, bl:
                                      _DLL.botan_pk_op_key_agreement(self.__obj, b, bl,
                                                                     other, len(other),
                                                                     salt, len(salt)))

class KemEncrypt:
    """Key encapsulation using a public key"""

    def __init__(self, key: PublicKey, params: str):
        """Create an encapsulation operation using ``key`` and the named KDF,
        eg "KDF2(SHA-256)", or "Raw" to return the shared secret directly."""
        self.__obj = c_void_p(0)
        _DLL.botan_pk_op_kem_encrypt_create(byref(self.__obj), key._handle(), _ctype_str(params))

    def __del__(self):
        _DLL.botan_pk_op_kem_encrypt_destroy(self.__obj)

    def shared_key_length(self, desired_key_len: int) -> int:
        """Returns the length of the shared key produced for a request of ``desired_key_len`` bytes"""
        return _call_fn_returning_sz(
            lambda len: _DLL.botan_pk_op_kem_encrypt_shared_key_length(self.__obj, desired_key_len, len))

    def encapsulated_key_length(self) -> int:
        """Returns the length of the encapsulated key"""
        return _call_fn_returning_sz(
            lambda len: _DLL.botan_pk_op_kem_encrypt_encapsulated_key_length(self.__obj, len))

    def create_shared_key(self, rng: RandomNumberGenerator, salt: bytes, desired_key_len: int) -> tuple[bytes, bytes]:
        """Generate a new shared key, returning a tuple of the shared key and the
        encapsulated key to be sent to the holder of the private key"""
        shared_key_len = self.shared_key_length(desired_key_len)
        shared_key_buf = create_string_buffer(shared_key_len)

        encapsulated_key_len = self.encapsulated_key_length()
        encapsulated_key_buf = create_string_buffer(encapsulated_key_len)

        _DLL.botan_pk_op_kem_encrypt_create_shared_key(
            self.__obj,
            rng._handle(),
            salt,
            len(salt),
            c_size_t(desired_key_len),
            shared_key_buf,
            c_size_t(shared_key_len),
            encapsulated_key_buf,
            c_size_t(encapsulated_key_len)
        )

        shared_key = shared_key_buf.raw[:]
        encapsulated_key = encapsulated_key_buf.raw[:]

        return (shared_key, encapsulated_key)

class KemDecrypt:
    """Key decapsulation using a private key"""

    def __init__(self, key: PrivateKey, params: str):
        """Create a decapsulation operation using ``key`` and the named KDF,
        eg "KDF2(SHA-256)", or "Raw" to return the shared secret directly."""
        self.__obj = c_void_p(0)
        _DLL.botan_pk_op_kem_decrypt_create(byref(self.__obj), key._handle(), _ctype_str(params))

    def __del__(self):
        _DLL.botan_pk_op_kem_decrypt_destroy(self.__obj)

    def shared_key_length(self, desired_key_len: int) -> int:
        """Returns the length of the shared key produced for a request of ``desired_key_len`` bytes"""
        return _call_fn_returning_sz(
            lambda len: _DLL.botan_pk_op_kem_decrypt_shared_key_length(self.__obj, desired_key_len, len))

    def decrypt_shared_key(self, salt: bytes, desired_key_len: int, encapsulated_key: bytes) -> bytes:
        """Recover the shared key from the encapsulated key"""
        shared_key_len = self.shared_key_length(desired_key_len)

        return _call_fn_returning_vec(
            shared_key_len,
            lambda b, bl: _DLL.botan_pk_op_kem_decrypt_shared_key(
                self.__obj,
                salt,
                len(salt),
                encapsulated_key,
                c_size_t(len(encapsulated_key)),
                c_size_t(desired_key_len),
                b,
                bl)
        )

def _load_buf_or_file(filename, buf, file_fn, buf_fn):
    if filename is None and buf is None:
        raise BotanException("No filename or buf given")
    if filename is not None and buf is not None:
        raise BotanException("Both filename and buf given")

    obj = c_void_p(0)

    if filename is not None:
        file_fn(byref(obj), _ctype_str(filename))
    elif buf is not None:
        bits = _ctype_bits(buf)
        buf_fn(byref(obj), bits, len(bits))

    return obj


#
# X.509 certificates
#
class X509Cert: # pylint: disable=invalid-name
    """Class representing an X.509 certificate.

    A certificate in PEM or DER format can be loaded from a file, with the ``filename``
    argument, or from a bytestring, with the ``buf`` argument."""

    def __init__(self, filename: str | None = None, buf: bytes | None = None):
        """Load a certificate from either a file or a bytestring, but not both"""
        self.__obj = c_void_p(0)
        self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_cert_load_file, _DLL.botan_x509_cert_load)

    def __del__(self):
        _DLL.botan_x509_cert_destroy(self.__obj)

    def time_starts(self) -> datetime:
        """Return the time the certificate becomes valid, as a string in form
        "YYYYMMDDHHMMSSZ" where Z is a literal character reflecting that this time is
        relative to UTC."""
        starts = _call_fn_returning_str(
            16, lambda b, bl: _DLL.botan_x509_cert_get_time_starts(self.__obj, b, bl))
        if len(starts) == 13:
            # UTC time
            struct_time = strptime(starts, "%y%m%d%H%M%SZ")
        elif len(starts) == 15:
            # Generalized time
            struct_time = strptime(starts, "%Y%m%d%H%M%SZ")
        else:
            raise BotanException("Unexpected date/time format for x509 start time")

        return datetime.fromtimestamp(mktime(struct_time))

    def time_expires(self) -> datetime:
        """Return the time the certificate expires, as a string in form
        "YYYYMMDDHHMMSSZ" where Z is a literal character reflecting that this time is
        relative to UTC."""
        expires = _call_fn_returning_str(
            16, lambda b, bl: _DLL.botan_x509_cert_get_time_expires(self.__obj, b, bl))
        if len(expires) == 13:
            # UTC time
            struct_time = strptime(expires, "%y%m%d%H%M%SZ")
        elif len(expires) == 15:
            # Generalized time
            struct_time = strptime(expires, "%Y%m%d%H%M%SZ")
        else:
            raise BotanException("Unexpected date/time format for x509 expire time")

        return datetime.fromtimestamp(mktime(struct_time))

    def to_string(self) -> str:
        """Format the certificate as a free-form string."""
        return _call_fn_viewing_str(
            lambda vc, vfn: _DLL.botan_x509_cert_view_as_string(self.__obj, vc, vfn))

    def fingerprint(self, hash_algo: str = 'SHA-256') -> str:
        """Return a fingerprint for the certificate, which is basically just a hash
        of the binary contents. Normally SHA-1 or SHA-256 is used, but any hash
        function is allowed."""
        n = HashFunction(hash_algo).output_length() * 3
        return _call_fn_returning_str(
            n, lambda b, bl: _DLL.botan_x509_cert_get_fingerprint(self.__obj, _ctype_str(hash_algo), b, bl))

    def serial_number(self) -> bytes:
        """Return the serial number of the certificate."""
        return _call_fn_returning_vec(
            32, lambda b, bl: _DLL.botan_x509_cert_get_serial_number(self.__obj, b, bl))

    def authority_key_id(self) -> bytes:
        """Return the authority key ID set in the certificate, which may be empty."""
        return _call_fn_returning_vec(
            32, lambda b, bl: _DLL.botan_x509_cert_get_authority_key_id(self.__obj, b, bl))

    def subject_key_id(self) -> bytes:
        """Return the subject key ID set in the certificate, which may be empty."""
        return _call_fn_returning_vec(
            32, lambda b, bl: _DLL.botan_x509_cert_get_subject_key_id(self.__obj, b, bl))

    def subject_public_key_bits(self) -> bytes:
        """Get the serialized representation of the public key included in this certificate."""
        return _call_fn_viewing_vec(
            lambda vc, vfn: _DLL.botan_x509_cert_view_public_key_bits(self.__obj, vc, vfn))

    def subject_public_key(self) -> PublicKey:
        """Get the public key included in this certificate as an object of class ``PublicKey``."""
        pub = c_void_p(0)
        _DLL.botan_x509_cert_get_public_key(self.__obj, byref(pub))
        return PublicKey(pub)

    def subject_dn(self, key: str, index: int) -> str:
        """Get a value from the subject DN field.

        ``key`` specifies a value to get, for instance ``"Name"`` or ``"Country"``."""
        return _call_fn_returning_str(
            0, lambda b, bl: _DLL.botan_x509_cert_get_subject_dn(self.__obj, _ctype_str(key), index, b, bl))

    def issuer_dn(self, key: str, index: int) -> str:
        """Get a value from the issuer DN field.

        ``key`` specifies a value to get, for instance ``"Name"`` or ``"Country"``."""
        return _call_fn_returning_str(
            0, lambda b, bl: _DLL.botan_x509_cert_get_issuer_dn(self.__obj, _ctype_str(key), index, b, bl))

    def hostname_match(self, hostname: str) -> bool:
        """Return True if the Common Name (CN) field of the certificate matches a given ``hostname``."""
        rc = _DLL.botan_x509_cert_hostname_match(self.__obj, _ctype_str(hostname))
        return rc == 0

    def not_before(self) -> int:
        """Return the time the certificate becomes valid, as seconds since epoch."""
        time = c_uint64(0)
        _DLL.botan_x509_cert_not_before(self.__obj, byref(time))
        return time.value

    def not_after(self) -> int:
        """Return the time the certificate expires, as seconds since epoch."""
        time = c_uint64(0)
        _DLL.botan_x509_cert_not_after(self.__obj, byref(time))
        return time.value

    def allowed_usage(self, usage_list: list[str]) -> bool:
        """Return True if the certificates Key Usage extension contains all constraints given in ``usage_list``.
        Also return True if the certificate doesn't have this extension.
        Example usage constraints are: ``"DIGITAL_SIGNATURE"``, ``"KEY_CERT_SIGN"``, ``"CRL_SIGN"``."""
        usage_values = {"NO_CONSTRAINTS": 0,
                        "DIGITAL_SIGNATURE": 32768,
                        "NON_REPUDIATION": 16384,
                        "KEY_ENCIPHERMENT": 8192,
                        "DATA_ENCIPHERMENT": 4096,
                        "KEY_AGREEMENT": 2048,
                        "KEY_CERT_SIGN": 1024,
                        "CRL_SIGN": 512,
                        "ENCIPHER_ONLY": 256,
                        "DECIPHER_ONLY": 128}
        usage = 0
        for u in usage_list:
            if u not in usage_values:
                return False
            usage += usage_values[u]

        rc = _DLL.botan_x509_cert_allowed_usage(self.__obj, c_uint(usage))
        return rc == 0

    def _handle(self):
        return self.__obj

    def ext_ip_addr_blocks(self) -> tuple[
        list[tuple[int | None, list[tuple[tuple[int], tuple[int]]] | None]],
        list[tuple[int | None, list[tuple[tuple[int], tuple[int]]] | None]]
    ]:
        """Get values from the IP Address Blocks extension.
        If the extension is not present, an exception will be raised.

        Returns all values in the extension, in the form of (v4, v6), where both contain a list of tuples of
        type (int | None, list[...]). The first element of each tuple is the SAFI, it may be ``None``
        to indicate no SAFI is present. The second element is a list of elements of type
        tuple[tuple[int], tuple[int]], where each element is a single address range. Each element contains
        two tuples of equal length, 4 for IPv4 families and 16 for IPv6 families. The values are the minimum
        and maximum addresses of the range respectively. If the particular family is marked as "inherit",
        the outer tuple will contain ``None`` as its second element instead of a list of ranges."""
        v4 = []
        v6 = []

        v4_count = c_size_t(0)
        v6_count = c_size_t(0)

        _DLL.botan_x509_ext_ip_addr_blocks_get_counts(self.__obj, byref(v4_count), byref(v6_count))

        v4_count = v4_count.value
        v6_count = v6_count.value

        for (ipv6, stop) in ((0, v4_count), (1, v6_count)):
            for i in range(stop):
                size = 16 if ipv6 else 4

                has_safi = c_int(0)
                safi = c_uint8(0)
                present = c_int(0)
                count = c_size_t(0)
                _DLL.botan_x509_ext_ip_addr_blocks_get_family(self.__obj, c_int(ipv6), c_size_t(i), byref(has_safi), byref(safi), byref(present), byref(count))
                ranges = None
                if present.value == 1:
                    ranges = []
                    for entry in range(count.value):
                        min_, max_ = _call_fn_returning_vec_pair(
                            size, size, lambda mi, _, ma, out_len, ipv6=ipv6, i=i, entry=entry: _DLL.botan_x509_ext_ip_addr_blocks_get_address(
                                self.__obj,
                                c_int(ipv6),
                                c_size_t(i),
                                c_size_t(entry),
                                mi,
                                ma,
                                out_len))
                        ranges.append((tuple(min_), tuple(max_)))

                safi = safi.value if has_safi.value == 1 else None
                if ipv6 == 0:
                    v4.append((safi, ranges))
                else:
                    v6.append((safi, ranges))
        return (v4, v6)

    def __get_as_blocks_values(self, asnum: bool) -> list[tuple[int, int]] | None:
        present = c_int(0)
        count = c_size_t(0)
        asnum = c_int(1 if asnum else 0)
        _DLL.botan_x509_ext_as_blocks_get_info(self.__obj, asnum, byref(present), byref(count))

        # value is 'inherit'
        if present.value == 0:
            return None

        values = []
        for i in range(count.value):
            min_ = c_uint32(0)
            max_ = c_uint32(0)
            _DLL.botan_x509_ext_as_blocks_get_entry_at(self.__obj, asnum, c_size_t(i), byref(min_), byref(max_))
            values.append((min_.value, max_.value))
        return values

    def ext_as_blocks_asnum(self) -> list[tuple[int, int]] | None:
        """Get values from the AS Blocks extension.
        If the extension is not present, an exception will be raised.

        Returns all AS numbers contained in the extension.
        Returns a list of tuples, where each tuple is a range, and its inner elements are the
        minimum and maximum values of the range respectively.
        If AS numbers are marked as "inherit", ``None`` is returned instead.
        If AS numbers are not present in the extension at all, this raises an exception."""
        return self.__get_as_blocks_values(True)

    def ext_as_blocks_rdi(self) -> list[tuple[int, int]] | None:
        """Get values from the AS Blocks extension.
        If the extension is not present, an exception will be raised.

        Get all RDIs contained in the extension.
        Returns a list of tuples, where each tuple is a range, and its inner elements are the
        minimum and maximum values of the range respectively.
        If RDIs are marked as "inherit", ``None`` is returned instead.
        If RDIs are not present in the extension at all, this raises an exception."""
        return self.__get_as_blocks_values(False)

    def verify(self,
               intermediates: list[X509Cert] | None = None,
               trusted: list[X509Cert] | None = None,
               trusted_path: str | None = None,
               required_strength: int = 0,
               hostname: str | None = None,
               reference_time: int = 0,
               crls: list[X509CRL] | None = None) -> int:
        """Verify a certificate. Returns 0 if validation was successful, returns a positive error code
        if the validation was unsuccessful.

        ``intermediates`` is a list of untrusted subauthorities.

        ``trusted`` is a list of trusted root CAs.

        The ``trusted_path`` refers to a directory where one or more trusted CA
        certificates are stored.

        Set ``required_strength`` to indicate the minimum key and hash strength
        that is allowed. For instance setting to 80 allows 1024-bit RSA and SHA-1.
        Setting to 110 requires 2048-bit RSA and SHA-256 or higher. Set to zero
        to accept a default.

        If ``hostname`` is given, it will be checked against the certificates CN field.

        Set ``reference_time`` to be the time which the certificate chain is
        validated against. Use zero (default) to use the current system clock.

        ``crls`` is a list of CRLs issued by either trusted or untrusted authorities."""

        if intermediates is not None:
            c_intermediates = len(intermediates) * c_void_p
            arr_intermediates = c_intermediates()
            for i, ca in enumerate(intermediates):
                arr_intermediates[i] = ca._handle()
            len_intermediates = c_size_t(len(intermediates))
        else:
            arr_intermediates = c_void_p(0)
            len_intermediates = c_size_t(0)

        if trusted is not None:
            c_trusted = len(trusted) * c_void_p
            arr_trusted = c_trusted()
            for i, ca in enumerate(trusted):
                arr_trusted[i] = ca._handle()
            len_trusted = c_size_t(len(trusted))
        else:
            arr_trusted = c_void_p(0)
            len_trusted = c_size_t(0)

        if crls is not None:
            c_crls = len(crls) * c_void_p
            arr_crls = c_crls()
            for i, crl in enumerate(crls):
                arr_crls[i] = crl._handle()
            len_crls = c_size_t(len(crls))
        else:
            arr_crls = c_void_p(0)
            len_crls = c_size_t(0)

        error_code = c_int(0)

        _DLL.botan_x509_cert_verify_with_crl(byref(error_code),
                                             self.__obj,
                                             byref(arr_intermediates),
                                             len_intermediates,
                                             byref(arr_trusted),
                                             len_trusted,
                                             byref(arr_crls),
                                             len_crls,
                                             _ctype_str(trusted_path),
                                             c_size_t(required_strength),
                                             _ctype_str(hostname),
                                             c_uint64(reference_time))

        return error_code.value

    @classmethod
    def validation_status(cls, error_code: int) -> str:
        """Return an informative string associated with the verification return code."""
        return _ctype_to_str(_DLL.botan_x509_cert_validation_status(c_int(error_code)))

    def is_revoked(self, crl: X509CRL) -> bool:
        """Check if the certificate (``self``) is revoked on the given ``crl``."""
        rc = _DLL.botan_x509_is_revoked(crl._handle(), self.__obj)
        return rc == 0


#
# X.509 Certificate revocation lists
#

class X509CRLReason(IntEnum):
    """The reason a certificate was revoked, as encoded in the CRL entry reason code"""

    #: No specific reason was given for the revocation.
    UNSPECIFIED = 0
    #: The subject's private key is known or suspected to have been compromised.
    KEY_COMPROMISE = 1
    #: A CA certificate's private key is known or suspected to have been compromised.
    CA_COMPROMISE = 2
    #: The subject's name or other information changed, without any suspicion of key compromise.
    AFFILIATION_CHANGED = 3
    #: The certificate has been superseded, without any suspicion of key compromise.
    SUPERSEDED = 4
    #: The certificate is no longer needed, without any suspicion of key compromise.
    CESSATION_OF_OPERATION = 5
    #: The certificate is temporarily suspended (placed on hold).
    CERTIFICATE_HOLD = 6
    #: Used only in delta CRLs to remove an entry from the base CRL (e.g. a hold was lifted).
    REMOVE_FROM_CRL = 8
    #: A privilege contained in the certificate has been withdrawn.
    PRIVILEGE_WITHDRAWN = 9
    #: An attribute authority's private key is known or suspected to have been compromised.
    AA_COMPROMISE = 10

    @classmethod
    def to_bits(cls, reason: X509CRLReason) -> int:
        """Returns the numeric encoding of ``reason``"""
        return reason.value

    @classmethod
    def from_bits(cls, reason: int) -> X509CRLReason:
        """Returns the reason corresponding to the numeric encoding ``reason``"""
        return cls(reason)


class X509CRLEntry:
    """A single revoked certificate, as recorded in a CRL"""

    def __init__(self):
        """Create an entry with no associated certificate; applications should use ``create``"""
        self.__obj = c_void_p(0)

    def __del__(self):
        _DLL.botan_x509_crl_entry_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    @classmethod
    def create(cls, cert: X509Cert, reason: X509CRLReason):
        """Create a new entry revoking ``cert`` for the given ``reason``"""
        entry = X509CRLEntry()
        _DLL.botan_x509_crl_entry_create(byref(entry._handle()), cert._handle(), X509CRLReason.to_bits(reason))
        return entry

    def serial_number(self) -> MPI:
        """Returns the serial number of the revoked certificate"""
        sn = c_void_p(0)
        _DLL.botan_x509_crl_entry_serial_number(self.__obj, byref(sn))
        return MPI(sn)

    def revocation_date(self) -> int:
        """Returns the time the certificate was revoked, as seconds since epoch"""
        time = c_uint64(0)
        _DLL.botan_x509_crl_entry_revocation_date(self.__obj, byref(time))
        return time.value

    def reason(self) -> X509CRLReason:
        """Returns the reason the certificate was revoked"""
        reason = c_int(0)
        _DLL.botan_x509_crl_entry_reason(self.__obj, byref(reason))
        return X509CRLReason.from_bits(reason.value)


class X509CRL:
    """Class representing an X.509 Certificate Revocation List.

    A CRL in PEM or DER format can be loaded from a file, with the ``filename`` argument,
    or from a bytestring, with the ``buf`` argument.
    """

    def __init__(self, filename: str | None = None, buf: bytes | None = None):
        """Load a CRL from either a file or a bytestring. If neither is given the object is empty."""
        if not filename and not buf:
            self.__obj = c_void_p(0)
        else:
            self.__obj = _load_buf_or_file(filename, buf, _DLL.botan_x509_crl_load_file, _DLL.botan_x509_crl_load)

    def __del__(self):
        _DLL.botan_x509_crl_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    @classmethod
    def create(
        cls,
        rng: RandomNumberGenerator,
        ca_cert: X509Cert,
        ca_key: PrivateKey,
        issue_time: int,
        next_update: int,
        hash_fn: str | None = None,
        padding: str | None = None
    ) -> X509CRL:
        """Create a new, empty CRL issued by ``ca_cert`` and signed using ``ca_key``.

        ``issue_time`` is the time the CRL becomes valid, in seconds since epoch, and
        ``next_update`` the number of seconds after that until the CRL expires.
        The signature ``hash_fn`` and ``padding`` may be None to use a default."""
        crl = X509CRL()
        _DLL.botan_x509_crl_create(
            byref(crl._handle()),
            rng._handle(),
            ca_cert._handle(),
            ca_key._handle(),
            issue_time,
            next_update,
            _ctype_str(hash_fn),
            _ctype_str(padding)
        )
        return crl

    def revoke(
        self,
        rng: RandomNumberGenerator,
        ca_cert: X509Cert,
        ca_key: PrivateKey,
        issue_time: int,
        next_update: int,
        new_entries: list[X509CRLEntry],
        hash_fn: str | None = None,
        padding: str | None = None
    ) -> X509CRL:
        """Returns a new CRL, again issued by ``ca_cert`` and signed using ``ca_key``,
        containing the entries of ``self`` plus ``new_entries``."""
        crl = X509CRL()
        c_revoked = len(new_entries) * c_void_p
        arr_new_entries = c_revoked()
        for i, entry in enumerate(new_entries):
            arr_new_entries[i] = entry._handle()
        new_entries_len = c_size_t(len(new_entries))

        _DLL.botan_x509_crl_update(
            byref(crl._handle()),
            self.__obj,
            rng._handle(),
            ca_cert._handle(),
            ca_key._handle(),
            issue_time,
            next_update,
            arr_new_entries,
            new_entries_len,
            _ctype_str(hash_fn),
            _ctype_str(padding)
        )
        return crl

    def revoked(self) -> list[X509CRLEntry]:
        """Returns the list of entries contained in this CRL"""
        count = c_size_t(0)
        _DLL.botan_x509_crl_entries_count(self.__obj, byref(count))
        revoked = []
        for i in range(count.value):
            entry = X509CRLEntry()
            _DLL.botan_x509_crl_entries(self.__obj, c_size_t(i), byref(entry._handle()))
            revoked.append(entry)
        return revoked

    def verify(self, key: PublicKey) -> bool:
        """Returns True if the signature on this CRL is valid for the given public key"""
        rc = _DLL.botan_x509_crl_verify_signature(self.__obj, key._handle())
        return rc == 1


class MPI:
    """Initialize an MPI object with specified value, left as zero otherwise. The
    ``initial_value`` should be an ``int``, ``str``, or ``MPI``.
    The ``radix`` value should be set to 16 when initializing from a base 16 ``str`` value.

    Most of the usual arithmetic operators (``__add__``, ``__mul__``, etc) are defined.
    """

    def __init__(self, initial_value: MPILike | c_void_p = None, radix: int | None = None):
        """Initialize an MPI, see the class documentation for the accepted values"""
        if isinstance(initial_value, c_void_p):
            self.__obj = initial_value
            return

        self.__obj = c_void_p(0)
        _DLL.botan_mp_init(byref(self.__obj))

        if initial_value is None:
            pass # left as zero
        elif isinstance(initial_value, MPI):
            _DLL.botan_mp_set_from_mp(self.__obj, initial_value._handle())
        elif radix is not None:
            _DLL.botan_mp_set_from_radix_str(self.__obj, _ctype_str(initial_value), c_size_t(radix))
        elif isinstance(initial_value, str):
            _DLL.botan_mp_set_from_str(self.__obj, _ctype_str(initial_value))
        else:
            # For int or long (or whatever else), try converting to string:
            _DLL.botan_mp_set_from_str(self.__obj, _ctype_str(str(initial_value)))

    @classmethod
    def random(cls, rng_obj: RandomNumberGenerator, bits: int) -> MPI:
        """Returns a new MPI with a random value of exactly ``bits`` bits"""
        bn = MPI()
        _DLL.botan_mp_rand_bits(bn._handle(), rng_obj._handle(), c_size_t(bits))
        return bn

    @classmethod
    def random_range(cls, rng_obj: RandomNumberGenerator, lower: MPI, upper: MPI):
        """Returns a new MPI with a random value in the range [``lower``, ``upper``)"""
        bn = MPI()
        _DLL.botan_mp_rand_range(bn._handle(), rng_obj._handle(), lower._handle(), upper._handle())
        return bn

    def __del__(self):
        _DLL.botan_mp_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    def __int__(self):
        hexv = _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_mp_view_hex(self.__obj, vc, vfn))
        return int(hexv, 16)

    def __repr__(self):
        return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_mp_view_str(self.__obj, 10, vc, vfn))

    def to_bytes(self) -> Array[c_char]:
        """Returns the big-endian binary encoding of this value"""
        byte_count = self.byte_count()
        out_len = c_size_t(byte_count)
        out = create_string_buffer(out_len.value)
        _DLL.botan_mp_to_bin(self.__obj, out, byref(out_len))
        assert out_len.value == byte_count
        return out

    def is_negative(self) -> bool:
        """Returns True if this value is less than zero"""
        rc = _DLL.botan_mp_is_negative(self.__obj)
        return rc == 1

    def is_positive(self) -> bool:
        """Returns True if this value is greater than or equal to zero"""
        rc = _DLL.botan_mp_is_positive(self.__obj)
        return rc == 1

    def is_zero(self) -> bool:
        """Returns True if this value is zero"""
        rc = _DLL.botan_mp_is_zero(self.__obj)
        return rc == 1

    def is_odd(self) -> bool:
        """Returns True if this value is odd"""
        return self.get_bit(0) == 1

    def is_even(self) -> bool:
        """Returns True if this value is even"""
        return self.get_bit(0) == 0

    def flip_sign(self):
        """Negate this value in place"""
        _DLL.botan_mp_flip_sign(self.__obj)

    def cmp(self, other: MPI) -> int:
        """Returns a negative number, zero, or a positive number if ``self`` is
        less than, equal to, or greater than ``other``"""
        r = c_int(0)
        _DLL.botan_mp_cmp(byref(r), self.__obj, other._handle())
        return r.value

    def __hash__(self):
        return hash(self.to_bytes())

    def __eq__(self, other: MPI | object) -> bool:
        if isinstance(other, MPI):
            return self.cmp(other) == 0
        else:
            return False

    def __ne__(self, other: MPI | object) -> bool:
        if isinstance(other, MPI):
            return self.cmp(other) != 0
        else:
            return False

    def __lt__(self, other: MPI | object) -> bool:
        if isinstance(other, MPI):
            return self.cmp(other) < 0
        else:
            return False

    def __le__(self, other: MPI | object) -> bool:
        if isinstance(other, MPI):
            return self.cmp(other) <= 0
        else:
            return False

    def __gt__(self, other: MPI | object) -> bool:
        if isinstance(other, MPI):
            return self.cmp(other) > 0
        else:
            return False

    def __ge__(self, other: MPI | object) -> bool:
        if isinstance(other, MPI):
            return self.cmp(other) >= 0
        else:
            return False

    def __add__(self, other: MPI):
        r = MPI()
        _DLL.botan_mp_add(r._handle(), self.__obj, other._handle())
        return r

    def __iadd__(self, other: MPI):
        _DLL.botan_mp_add(self.__obj, self.__obj, other._handle())
        return self

    def __sub__(self, other: MPI):
        r = MPI()
        _DLL.botan_mp_sub(r._handle(), self.__obj, other._handle())
        return r

    def __isub__(self, other: MPI):
        _DLL.botan_mp_sub(self.__obj, self.__obj, other._handle())
        return self

    def __mul__(self, other: MPI):
        r = MPI()
        _DLL.botan_mp_mul(r._handle(), self.__obj, other._handle())
        return r

    def __imul__(self, other: MPI):
        _DLL.botan_mp_mul(self.__obj, self.__obj, other._handle())
        return self

    def __divmod__(self, other: MPI):
        d = MPI()
        q = MPI()
        _DLL.botan_mp_div(d._handle(), q._handle(), self.__obj, other._handle())
        return (d, q)

    def __mod__(self, other: MPI):
        d = MPI()
        q = MPI()
        _DLL.botan_mp_div(d._handle(), q._handle(), self.__obj, other._handle())
        return q

    def __lshift__(self, shift: int):
        r = MPI()
        _DLL.botan_mp_lshift(r._handle(), self.__obj, c_size_t(shift))
        return r

    def __ilshift__(self, shift: int):
        _DLL.botan_mp_lshift(self.__obj, self.__obj, c_size_t(shift))
        return self

    def __rshift__(self, shift: int):
        r = MPI()
        _DLL.botan_mp_rshift(r._handle(), self.__obj, c_size_t(shift))
        return r

    def __irshift__(self, shift: int):
        _DLL.botan_mp_rshift(self.__obj, self.__obj, c_size_t(shift))
        return self

    def mod_mul(self, other: MPI, modulus: MPI) -> MPI:
        """Return the multiplication product of ``self`` and ``other`` modulo ``modulus``"""
        r = MPI()
        _DLL.botan_mp_mod_mul(r._handle(), self.__obj, other._handle(), modulus._handle())
        return r

    def gcd(self, other: MPI) -> MPI:
        """Return the greatest common divisor of ``self`` and ``other``"""
        r = MPI()
        _DLL.botan_mp_gcd(r._handle(), self.__obj, other._handle())
        return r

    def pow_mod(self, exponent: MPI, modulus: MPI) -> MPI:
        """Return ``self`` to the ``exponent`` power modulo ``modulus``"""
        r = MPI()
        _DLL.botan_mp_powmod(r._handle(), self.__obj, exponent._handle(), modulus._handle())
        return r

    def is_prime(self, rng_obj: RandomNumberGenerator, prob: int = 128) -> bool:
        """Test if ``self`` is prime"""
        return _DLL.botan_mp_is_prime(self.__obj, rng_obj._handle(), c_size_t(prob)) == 1

    def inverse_mod(self, modulus: MPI) -> MPI:
        """Return the inverse of ``self`` modulo ``modulus``, or zero if no inverse exists"""
        r = MPI()
        _DLL.botan_mp_mod_inverse(r._handle(), self.__obj, modulus._handle())
        return r

    def bit_count(self) -> int:
        """Returns the size of this value in bits"""
        b = c_size_t(0)
        _DLL.botan_mp_num_bits(self.__obj, byref(b))
        return b.value

    def byte_count(self) -> int:
        """Returns the size of this value in bytes"""
        b = c_size_t(0)
        _DLL.botan_mp_num_bytes(self.__obj, byref(b))
        return b.value

    def get_bit(self, bit: int) -> bool:
        """Returns the value of the specified bit"""
        return _DLL.botan_mp_get_bit(self.__obj, c_size_t(bit)) == 1

    def clear_bit(self, bit: int):
        """Set the specified bit to zero"""
        _DLL.botan_mp_clear_bit(self.__obj, c_size_t(bit))

    def set_bit(self, bit: int):
        """Set the specified bit to one"""
        _DLL.botan_mp_set_bit(self.__obj, c_size_t(bit))


class OID:
    """An ASN.1 object identifier"""

    def __init__(self, obj: c_void_p | None = None):
        """Create an OID wrapping the given FFI handle, or an empty one.
        Applications should use ``from_string`` instead."""
        if not obj:
            obj = c_void_p(0)
        self.__obj = obj

    def __del__(self):
        _DLL.botan_oid_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    @classmethod
    def from_string(cls, value: str) -> OID:
        """Create a new OID from dot notation or from a known name"""
        oid = OID()
        _DLL.botan_oid_from_string(byref(oid._handle()), _ctype_str(value))
        return oid

    def to_string(self) -> str:
        """Export the OID in dot notation"""
        return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_oid_view_string(self.__obj, vc, vfn))

    def to_name(self) -> str:
        """Export the OID as a name if it has one, else in dot notation"""
        return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_oid_view_name(self.__obj, vc, vfn))

    def register(self, name: str):
        """Register the OID so that it may later be retrieved by the given name"""
        _DLL.botan_oid_register(self.__obj, _ctype_str(name))

    def cmp(self, other: OID) -> int:
        """Returns a negative number, zero, or a positive number if ``self`` sorts
        before, equal to, or after ``other``"""
        r = c_int(0)
        _DLL.botan_oid_cmp(byref(r), self.__obj, other._handle())
        return r.value

    def __eq__(self, other: OID | object) -> bool:
        if isinstance(other, OID):
            return self.cmp(other) == 0
        else:
            return False

    def __ne__(self, other: OID | object) -> bool:
        if isinstance(other, OID):
            return self.cmp(other) != 0
        else:
            return False

    def __lt__(self, other: OID | object) -> bool:
        if isinstance(other, OID):
            return self.cmp(other) < 0
        else:
            return False

    def __le__(self, other: OID | object) -> bool:
        if isinstance(other, OID):
            return self.cmp(other) <= 0
        else:
            return False

    def __gt__(self, other: OID | object) -> bool:
        if isinstance(other, OID):
            return self.cmp(other) > 0
        else:
            return False

    def __ge__(self, other: OID | object) -> bool:
        if isinstance(other, OID):
            return self.cmp(other) >= 0
        else:
            return False


class ECGroup:
    """An elliptic curve group"""

    def __init__(self, obj: c_void_p | None = None):
        """Create an ECGroup wrapping the given FFI handle, or an empty one.
        Applications should use one of the ``from_*`` methods instead."""
        if not obj:
            obj = c_void_p(0)
        self.__obj = obj

    def _handle(self):
        return self.__obj

    def __del__(self):
        _DLL.botan_ec_group_destroy(self.__obj)

    @classmethod
    def supports_application_specific_group(cls) -> bool:
        """Returns true if in this build configuration it is possible
        to register an application specific elliptic curve"""
        r = c_int(0)
        _DLL.botan_ec_group_supports_application_specific_group(byref(r))
        if r.value == 0:
            return False
        return True

    @classmethod
    def supports_named_group(cls, name: str) -> bool:
        """Returns true if in this build configuration ``ECGroup.from_name(name)`` will succeed"""
        r = c_int(0)
        _DLL.botan_ec_group_supports_named_group(_ctype_str(name), byref(r))
        if r.value == 0:
            return False
        return True

    @classmethod
    def from_params(cls, oid: OID, p: MPI, a: MPI, b: MPI, base_x: MPI, base_y: MPI, order: MPI) -> ECGroup:
        """Creates a new ECGroup from ec parameters"""
        ec_group = ECGroup()
        _DLL.botan_ec_group_from_params(
            byref(ec_group._handle()),
            oid._handle(),
            p._handle(),
            a._handle(),
            b._handle(),
            base_x._handle(),
            base_y._handle(),
            order._handle()
        )
        return ec_group

    @classmethod
    def from_ber(cls, ber: bytes) -> ECGroup:
        """Creates a new ECGroup from a BER blob"""
        ec_group = ECGroup()
        _DLL.botan_ec_group_from_ber(byref(ec_group._handle()), ber, len(ber))
        return ec_group

    @classmethod
    def from_pem(cls, pem: str) -> ECGroup:
        """Creates a new ECGroup from a PEM encoding"""
        ec_group = ECGroup()
        _DLL.botan_ec_group_from_pem(byref(ec_group._handle()), _ctype_str(pem))
        return ec_group

    @classmethod
    def from_oid(cls, oid: OID) -> ECGroup:
        """Creates a new ECGroup from a group named by an OID"""
        ec_group = ECGroup()
        _DLL.botan_ec_group_from_oid(byref(ec_group._handle()), oid._handle())
        return ec_group

    @classmethod
    def from_name(cls, name: str) -> ECGroup:
        """Creates a new ECGroup from a common group name"""
        ec_group = ECGroup()
        _DLL.botan_ec_group_from_name(byref(ec_group._handle()), _ctype_str(name))
        return ec_group

    @classmethod
    def unregister(cls, oid: OID) -> bool:
        """Unregister a previously registered group"""
        rc = _DLL.botan_ec_group_unregister(oid._handle())
        if rc == 1:
            return True
        return False

    def to_der(self) -> bytes:
        """Export the group in DER encoding"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_group_view_der(self.__obj, vc, vfn))

    def to_pem(self) -> str:
        """Export the group in PEM encoding"""
        return _call_fn_viewing_str(lambda vc, vfn: _DLL.botan_ec_group_view_pem(self.__obj, vc, vfn))

    def get_curve_oid(self) -> OID:
        """Get the curve OID"""
        oid = OID()
        _DLL.botan_ec_group_get_curve_oid(byref(oid._handle()), self.__obj)
        return oid

    def get_p(self) -> MPI:
        """Get the prime modulus of the field"""
        p = c_void_p(0)
        _DLL.botan_ec_group_get_p(byref(p), self.__obj)
        return MPI(p)

    def get_a(self) -> MPI:
        """Get the a parameter of the elliptic curve equation"""
        a = c_void_p(0)
        _DLL.botan_ec_group_get_a(byref(a), self.__obj)
        return MPI(a)

    def get_b(self) -> MPI:
        """Get the b parameter of the elliptic curve equation"""
        b = c_void_p(0)
        _DLL.botan_ec_group_get_b(byref(b), self.__obj)
        return MPI(b)

    def get_g_x(self) -> MPI:
        """Get the x coordinate of the base point"""
        g_x = c_void_p(0)
        _DLL.botan_ec_group_get_g_x(byref(g_x), self.__obj)
        return MPI(g_x)

    def get_g_y(self) -> MPI:
        """Get the y coordinate of the base point"""
        g_y = c_void_p(0)
        _DLL.botan_ec_group_get_g_y(byref(g_y), self.__obj)
        return MPI(g_y)

    def get_order(self) -> MPI:
        """Get the order of the base point"""
        order = c_void_p(0)
        _DLL.botan_ec_group_get_order(byref(order), self.__obj)
        return MPI(order)

    def get_identity(self) -> ECPoint:
        """Returns the identity element of this group"""
        return ECPoint.identity(self)

    def get_generator(self) -> ECPoint:
        """Returns the base point of this group"""
        return ECPoint.generator(self)

    def __eq__(self, other: ECGroup | object) -> bool:
        if isinstance(other, ECGroup):
            return _DLL.botan_ec_group_equal(self.__obj, other._handle()) == 1
        else:
            return False

    def __ne__(self, other: ECGroup | object) -> bool:
        return not self == other


class ECScalar:
    """An integer modulo the order of an elliptic curve group"""

    def __init__(self, obj: c_void_p | None = None):
        """Create an ECScalar wrapping the given FFI handle, or an empty one.
        Applications should use ``random`` or ``from_mpi`` instead."""
        if not obj:
            obj = c_void_p(0)
        self.__obj = obj

    def _handle(self):
        return self.__obj

    def __del__(self):
        _DLL.botan_ec_scalar_destroy(self.__obj)

    @classmethod
    def random(cls, group: ECGroup, rng: RandomNumberGenerator) -> ECScalar:
        """Create a new scalar with a random value"""
        scalar = ECScalar()
        _DLL.botan_ec_scalar_random(byref(scalar._handle()), group._handle(), rng._handle())
        return scalar

    @classmethod
    def from_mpi(cls, group: ECGroup, mpi: MPI) -> ECScalar:
        """Convert from an MPI to a scalar. Raises an exception if the MPI is negative or too large."""
        scalar = ECScalar()
        _DLL.botan_ec_scalar_from_mp(byref(scalar._handle()), group._handle(), mpi._handle())
        return scalar

    def to_mpi(self) -> MPI:
        """Convert from a scalar to an MPI."""
        obj = c_void_p(0)
        _DLL.botan_ec_scalar_to_mp(self.__obj, byref(obj))
        return MPI(obj)


class ECPoint:
    """A point on an elliptic curve"""

    def __init__(self, obj: c_void_p | None = None):
        """Create an ECPoint wrapping the given FFI handle, or an empty one.
        Applications should use one of the ``from_*`` methods instead."""
        if not obj:
            obj = c_void_p(0)
        self.__obj = obj

    def _handle(self):
        return self.__obj

    def __del__(self):
        _DLL.botan_ec_point_destroy(self.__obj)

    @classmethod
    def identity(cls, group: ECGroup) -> ECPoint:
        """Create a point set to the group identity"""
        ec_point = ECPoint()
        _DLL.botan_ec_point_identity(byref(ec_point._handle()), group._handle())
        return ec_point

    @classmethod
    def generator(cls, group: ECGroup) -> ECPoint:
        """Create a point set to the group generator"""
        ec_point = ECPoint()
        _DLL.botan_ec_point_generator(byref(ec_point._handle()), group._handle())
        return ec_point

    @classmethod
    def from_xy(cls, group: ECGroup, x: MPI, y: MPI) -> ECPoint:
        """Create a point from a set of (x,y) integers.
        The integers must be within the field and must satisfy the curve equation."""
        ec_point = ECPoint()
        _DLL.botan_ec_point_from_xy(byref(ec_point._handle()), group._handle(), x._handle(), y._handle())
        return ec_point

    @classmethod
    def from_bytes(cls, group: ECGroup, buf: bytes) -> ECPoint:
        """Create a point from a SEC1 compressed or uncompressed format."""
        ec_point = ECPoint()
        _DLL.botan_ec_point_from_bytes(byref(ec_point._handle()), group._handle(), buf, len(buf))
        return ec_point

    def __eq__(self, other: ECPoint | object) -> bool:
        if isinstance(other, ECPoint):
            return _DLL.botan_ec_point_equal(self.__obj, other._handle()) == 1
        else:
            return False

    def __ne__(self, other: ECPoint | object) -> bool:
        return not self == other

    def __add__(self, other: ECPoint):
        r = ECPoint()
        _DLL.botan_ec_point_add(byref(r._handle()), self.__obj, other._handle())
        return r

    def is_identity(self):
        """Returns True if this point is the identity element of its group"""
        return _DLL.botan_ec_point_is_identity(self.__obj) == 1

    def negate(self) -> ECPoint:
        """Returns the negation of this point"""
        r = ECPoint()
        _DLL.botan_ec_point_negate(byref(r._handle()), self.__obj)
        return r

    def mul(self, scalar: ECScalar, rng: RandomNumberGenerator) -> ECPoint:
        """Returns the product of this point and ``scalar``. The RNG is used for blinding."""
        r = ECPoint()
        _DLL.botan_ec_point_mul(byref(r._handle()), self.__obj, scalar._handle(), rng._handle())
        return r

    def to_x_bytes(self) -> bytes:
        """Get the fixed length encoding of the affine x coordinate"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_point_view_x_bytes(self.__obj, vc, vfn))

    def to_y_bytes(self) -> bytes:
        """Get the fixed length encoding of the affine y coordinate"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_point_view_y_bytes(self.__obj, vc, vfn))

    def to_xy_bytes(self) -> bytes:
        """Get the fixed length encoding of the affine x and y coordinates"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_point_view_xy_bytes(self.__obj, vc, vfn))

    def to_uncompressed(self) -> bytes:
        """Get the fixed length SEC1 uncompressed encoding"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_point_view_uncompressed(self.__obj, vc, vfn))

    def to_compressed(self) -> bytes:
        """Get the fixed length SEC1 compressed encoding"""
        return _call_fn_viewing_vec(lambda vc, vfn: _DLL.botan_ec_point_view_compressed(self.__obj, vc, vfn))


class FormatPreservingEncryptionFE1:
    """Initialize an instance for format preserving encryption"""

    def __init__(self, modulus: MPI, key: bytes, rounds: int = 5, compat_mode: bool = False):
        """Create an instance encrypting integers modulo ``modulus`` under ``key``"""
        flags = c_uint32(1 if compat_mode else 0)
        self.__obj = c_void_p(0)
        _DLL.botan_fpe_fe1_init(byref(self.__obj), modulus._handle(), key, len(key), c_size_t(rounds), flags)

    def __del__(self):
        _DLL.botan_fpe_destroy(self.__obj)

    def encrypt(self, msg: MPILike, tweak: str | bytes) -> MPI:
        """The msg should be a ``botan3.MPI`` or an object which can be converted to one"""
        r = MPI(msg)
        bits = _ctype_bits(tweak)
        _DLL.botan_fpe_encrypt(self.__obj, r._handle(), bits, len(bits))
        return r

    def decrypt(self, msg: MPILike, tweak: str | bytes) -> MPI:
        """The msg should be a ``botan3.MPI`` or an object which can be converted to one"""
        r = MPI(msg)
        bits = _ctype_bits(tweak)
        _DLL.botan_fpe_decrypt(self.__obj, r._handle(), bits, len(bits))
        return r

class HOTP:
    """Counter based one time passwords (RFC 4226)"""

    def __init__(self, key: bytes, digest: str = "SHA-1", digits: int = 6):
        """Create an HOTP instance using the given key, hash function, and number of digits"""
        self.__obj = c_void_p(0)
        _DLL.botan_hotp_init(byref(self.__obj), key, len(key), _ctype_str(digest), digits)

    def __del__(self):
        _DLL.botan_hotp_destroy(self.__obj)

    def generate(self, counter: int) -> int:
        """Generate an HOTP code for the provided counter"""
        code = c_uint32(0)
        _DLL.botan_hotp_generate(self.__obj, byref(code), counter)
        return code.value

    def check(self, code: int, counter: int, resync_range: int = 0) -> tuple[bool, int]:
        """Check if provided ``code`` is the correct code for ``counter``.
        If ``resync_range`` is greater than zero, HOTP also checks
        up to ``resync_range`` following counter values.

        Returns a tuple of (bool,int) where the boolean indicates if the
        code was valid, and the int indicates the next counter value
        that should be used. If the code did not verify, the next
        counter value is always identical to the counter that was passed
        in. If the code did verify and resync_range was zero, then the
        next counter will always be counter+1."""
        next_ctr = c_uint64(0)
        rc = _DLL.botan_hotp_check(self.__obj, byref(next_ctr), code, counter, resync_range)
        if rc == 0:
            return (True, next_ctr.value)
        else:
            return (False, counter)

class TOTP:
    """Time based one time passwords (RFC 6238)"""

    def __init__(self, key: bytes, digest: str = "SHA-1", digits: int = 6, timestep: int = 30):
        """Create a TOTP instance using the given key, hash function, number of digits,
        and time step in seconds"""
        self.__obj = c_void_p(0)
        _DLL.botan_totp_init(byref(self.__obj), key, len(key), _ctype_str(digest), digits, timestep)

    def __del__(self):
        _DLL.botan_totp_destroy(self.__obj)

    def generate(self, timestamp: int | None = None) -> int:
        """Generate the TOTP code for the given Unix timestamp, or for the current time"""
        if timestamp is None:
            timestamp = int(system_time())
        code = c_uint32(0)
        _DLL.botan_totp_generate(self.__obj, byref(code), timestamp)
        return code.value

    def check(self, code: int, timestamp: int | None = None, acceptable_drift: int = 0) -> bool:
        """Returns True if ``code`` is the correct code for the given Unix timestamp
        (or for the current time), allowing up to ``acceptable_drift`` time steps of
        clock skew in either direction"""
        if timestamp is None:
            timestamp = int(system_time())
        rc = _DLL.botan_totp_check(self.__obj, code, timestamp, acceptable_drift)
        if rc == 0:
            return True
        return False

def nist_key_wrap(kek: bytes, key: bytes, cipher: str | None = None) -> bytes:
    """Wrap ``key`` under the key encryption key ``kek`` using the NIST SP 800-38F KW mode.
    The input length must be a multiple of 8 bytes. If ``cipher`` is not specified,
    AES with a key length matching ``kek`` is used."""
    cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher
    padding = 0
    output = create_string_buffer(len(key) + 8)
    out_len = c_size_t(len(output))
    _DLL.botan_nist_kw_enc(_ctype_str(cipher_algo), padding,
                           key, len(key),
                           kek, len(kek),
                           output, byref(out_len))
    return bytes(output[0:int(out_len.value)])

def nist_key_unwrap(kek: bytes, wrapped: bytes, cipher: str | None = None) -> bytes:
    """Unwrap a key which was wrapped using ``nist_key_wrap``"""
    cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher
    padding = 0
    output = create_string_buffer(len(wrapped))
    out_len = c_size_t(len(output))
    _DLL.botan_nist_kw_dec(_ctype_str(cipher_algo), padding,
                           wrapped, len(wrapped),
                           kek, len(kek),
                           output, byref(out_len))
    return bytes(output[0:int(out_len.value)])

def nist_key_wrap_padded(kek: bytes, key: bytes, cipher: str | None = None) -> bytes:
    """Wrap ``key`` under the key encryption key ``kek`` using the NIST SP 800-38F KWP mode,
    which accepts an input of any length. If ``cipher`` is not specified, AES with a
    key length matching ``kek`` is used."""
    cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher
    padding = 1
    output = create_string_buffer(len(key) + 15)
    out_len = c_size_t(len(output))
    _DLL.botan_nist_kw_enc(_ctype_str(cipher_algo), padding,
                           key, len(key),
                           kek, len(kek),
                           output, byref(out_len))
    return bytes(output[0:int(out_len.value)])

def nist_key_unwrap_padded(kek: bytes, wrapped: bytes, cipher: str | None = None) -> bytes:
    """Unwrap a key which was wrapped using ``nist_key_wrap_padded``"""
    cipher_algo = "AES-%d" % (8*len(kek)) if cipher is None else cipher
    padding = 1
    output = create_string_buffer(len(wrapped))
    out_len = c_size_t(len(output))
    _DLL.botan_nist_kw_dec(_ctype_str(cipher_algo), padding,
                           wrapped, len(wrapped),
                           kek, len(kek),
                           output, byref(out_len))
    return bytes(output[0:int(out_len.value)])

class Srp6ServerSession:
    """The server side of the SRP-6a password authenticated key exchange"""

    def __init__(self, group: str):
        """Create a session using the named group (eg "modp/srp/2048")"""
        self.__obj = c_void_p(0)
        _DLL.botan_srp6_server_session_init(byref(self.__obj))
        self.__group = group
        self.__group_size = _call_fn_returning_sz(
            lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len))

    def __del__(self):
        obj = getattr(self, '_Srp6ServerSession__obj', None)
        self.__obj = c_void_p(0)
        if obj:
            _DLL.botan_srp6_server_session_destroy(obj)

    def __copy__(self):
        raise TypeError('Srp6ServerSession objects cannot be copied')

    def __deepcopy__(self, _memo):
        raise TypeError('Srp6ServerSession objects cannot be copied')

    def step1(self, verifier: bytes, hsh: str, rng: RandomNumberGenerator) -> bytes:
        """Given the verifier stored for this user, returns the value B to send to the client"""
        return _call_fn_returning_vec(self.__group_size,
                                      lambda b, bl:
                                      _DLL.botan_srp6_server_session_step1(self.__obj,
                                                                           verifier, len(verifier),
                                                                           _ctype_str(self.__group),
                                                                           _ctype_str(hsh),
                                                                           rng._handle(),
                                                                           b, bl))

    def step2(self, a: bytes) -> bytes:
        """Given the value A received from the client, returns the shared session key"""
        return _call_fn_returning_vec(self.__group_size, lambda k, kl:
                                      _DLL.botan_srp6_server_session_step2(self.__obj,
                                                                           a, len(a),
                                                                           k, kl))

def srp6_generate_verifier(identifier: str, password: str, salt: bytes, group: str, hsh: str) -> bytes:
    """Returns the verifier which the server stores for this user"""
    sz = _call_fn_returning_sz(lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len))

    return _call_fn_returning_vec(sz, lambda v, vl:
                                  _DLL.botan_srp6_generate_verifier(_ctype_str(identifier),
                                                                    _ctype_str(password),
                                                                    salt, len(salt),
                                                                    _ctype_str(group),
                                                                    _ctype_str(hsh),
                                                                    v, vl))

def srp6_client_agree(username: str, password: str, group: str, hsh: str, salt: bytes, b: bytes, rng: RandomNumberGenerator) -> tuple[bytes, bytes]:
    """The client side of the SRP-6a password authenticated key exchange.
    Given the value ``b`` received from the server, returns a tuple of the value A
    to send to the server and the shared session key."""
    sz = _call_fn_returning_sz(lambda len: _DLL.botan_srp6_group_size(_ctype_str(group), len))

    return _call_fn_returning_vec_pair(sz, sz, lambda a, al, k, kl:
                                       _DLL.botan_srp6_client_agree(_ctype_str(username),
                                                                    _ctype_str(password),
                                                                    _ctype_str(group),
                                                                    _ctype_str(hsh),
                                                                    salt, len(salt),
                                                                    b, len(b),
                                                                    rng._handle(),
                                                                    a, al,
                                                                    k, kl))

class Spake2pParams:
    """
    SPAKE2+ (RFC 9383) system parameters, selecting the elliptic curve
    group, the SPAKE2+ M/N group elements, and the hash function.

    The ciphersuite is one of "P256-SHA256", "P256-SHA512", "P384-SHA256",
    "P384-SHA512", or "P521-SHA512".
    """
    def __init__(self, ciphersuite: str | None = None):
        """Create system parameters for one of the named ciphersuites"""
        self.__obj = c_void_p(0)
        if ciphersuite is not None:
            _DLL.botan_spake2p_params_init(byref(self.__obj), _ctype_str(ciphersuite))

    @classmethod
    def custom(cls, group: ECGroup, seed: bytes, hash_fn: str) -> Spake2pParams:
        """
        Create custom system parameters for an arbitrary group, deriving the
        M/N group elements from the seed using hash to curve (which not all
        groups support). Both peers must use the same group, seed, and hash.
        """
        params = Spake2pParams()
        _DLL.botan_spake2p_params_init_custom(byref(params._handle()),
                                              group._handle(),
                                              seed, len(seed),
                                              _ctype_str(hash_fn))
        return params

    def __del__(self):
        _DLL.botan_spake2p_params_destroy(self.__obj)

    def _handle(self):
        return self.__obj

    def share_size(self) -> int:
        """
        Return the size in bytes of a key share (shareP or shareV)
        """
        return _call_fn_returning_sz(lambda sz: _DLL.botan_spake2p_params_share_size(self.__obj, sz))

    def confirmation_size(self) -> int:
        """
        Return the size in bytes of a key confirmation message (confirmP or confirmV)
        """
        return _call_fn_returning_sz(lambda sz: _DLL.botan_spake2p_params_confirmation_size(self.__obj, sz))

def spake2p_derive_secret(params: Spake2pParams, password: str, prover_id: bytes = b'', verifier_id: bytes = b'', salt: bytes = b'') -> bytes:
    """
    Derive a SPAKE2+ (RFC 9383) prover secret from a password, using Argon2id.

    The returned secret is password equivalent, and must be protected
    accordingly. It is used with `spake2p_registration_record` and
    `Spake2pProver`
    """
    return _call_fn_viewing_vec(lambda vc, vf:
                                _DLL.botan_spake2p_derive_secret(params._handle(),
                                                                 _ctype_str(password),
                                                                 prover_id, len(prover_id),
                                                                 verifier_id, len(verifier_id),
                                                                 salt, len(salt),
                                                                 vc, vf))

def spake2p_registration_record(params: Spake2pParams, secret: bytes, rng: RandomNumberGenerator) -> bytes:
    """
    Compute a SPAKE2+ registration record from a prover secret.

    The registration record is provided to the verifier during registration.
    """
    return _call_fn_viewing_vec(lambda vc, vf:
                                _DLL.botan_spake2p_registration_record(params._handle(),
                                                                       rng._handle(),
                                                                       secret, len(secret),
                                                                       vc, vf))

class Spake2pProver:
    """
    SPAKE2+ (RFC 9383) prover: the side which knows the password secret.

    The expected message flow is

    * The prover calls generate_message and sends the result to the verifier
    * The verifier calls process_message on it and sends the result to the prover
    * The prover calls process_message on it, verifying the verifier's key
      confirmation, and sends the resulting confirmation to the verifier
    * The verifier calls verify_confirmation on it

    After the final step both sides can call shared_secret.
    """
    def __init__(self, params: Spake2pParams, secret: bytes, prover_id: bytes = b'', verifier_id: bytes = b'', context: bytes = b''):
        """
        The identities and context must be agreed upon by both parties;
        the identities must additionally match the values used when
        deriving the prover secret.
        """
        self.__obj = c_void_p(0)
        _DLL.botan_spake2p_prover_init(byref(self.__obj),
                                       params._handle(),
                                       secret, len(secret),
                                       prover_id, len(prover_id),
                                       verifier_id, len(verifier_id),
                                       context, len(context))

    def __del__(self):
        _DLL.botan_spake2p_prover_destroy(self.__obj)

    def generate_message(self, rng: RandomNumberGenerator) -> bytes:
        """
        Generate the prover's key share, which is sent to the verifier.
        Can be called only once.
        """
        return _call_fn_viewing_vec(lambda vc, vf:
                                    _DLL.botan_spake2p_prover_generate_message(self.__obj, rng._handle(), vc, vf))

    def process_message(self, peer_message: bytes, rng: RandomNumberGenerator) -> bytes:
        """
        Consume the verifier's response and return the prover's key
        confirmation, which is sent to the verifier. Raises an exception
        if the verifier's key confirmation is wrong, typically meaning
        the passwords do not match.
        """
        return _call_fn_viewing_vec(lambda vc, vf:
                                    _DLL.botan_spake2p_prover_process_message(self.__obj, rng._handle(),
                                                                              peer_message, len(peer_message),
                                                                              vc, vf))

    def shared_secret(self) -> bytes:
        """
        Return the shared secret. Only valid after process_message succeeded.
        """
        return _call_fn_viewing_vec(lambda vc, vf:
                                    _DLL.botan_spake2p_prover_shared_secret(self.__obj, vc, vf))

class Spake2pVerifier:
    """
    SPAKE2+ (RFC 9383) verifier: the side which stores only the registration
    record derived from the password. See Spake2pProver for the message flow.
    """
    def __init__(self, params: Spake2pParams, record: bytes, prover_id: bytes = b'', verifier_id: bytes = b'', context: bytes = b''):
        """
        The identities and context must be agreed upon by both parties;
        the identities must additionally match the values used when
        deriving the prover secret.
        """
        self.__obj = c_void_p(0)
        _DLL.botan_spake2p_verifier_init(byref(self.__obj),
                                         params._handle(),
                                         record, len(record),
                                         prover_id, len(prover_id),
                                         verifier_id, len(verifier_id),
                                         context, len(context))

    def __del__(self):
        _DLL.botan_spake2p_verifier_destroy(self.__obj)

    def process_message(self, peer_message: bytes, rng: RandomNumberGenerator) -> bytes:
        """
        Consume the prover's key share and return the verifier's response
        (its own key share followed by a key confirmation), which is sent
        to the prover. Can be called only once.
        """
        return _call_fn_viewing_vec(lambda vc, vf:
                                    _DLL.botan_spake2p_verifier_process_message(self.__obj, rng._handle(),
                                                                                peer_message, len(peer_message),
                                                                                vc, vf))

    def verify_confirmation(self, confirmation: bytes) -> None:
        """
        Check the prover's key confirmation. Raises an exception if the
        confirmation is wrong, meaning the prover does not know the password.
        """
        _DLL.botan_spake2p_verifier_verify_confirmation(self.__obj, confirmation, len(confirmation))

    def skip_confirmation(self) -> None:
        """
        Skip checking the prover's key confirmation, allowing shared_secret
        to be called without verify_confirmation. After calling this, no
        evidence has been received that the peer knows the password; it is
        intended solely for protocols which embed SPAKE2+ and perform the
        prover's key confirmation themselves.
        """
        _DLL.botan_spake2p_verifier_skip_confirmation(self.__obj)

    def shared_secret(self) -> bytes:
        """
        Return the shared secret. Only valid after verify_confirmation
        succeeded, or after skip_confirmation.
        """
        return _call_fn_viewing_vec(lambda vc, vf:
                                    _DLL.botan_spake2p_verifier_shared_secret(self.__obj, vc, vf))

def zfec_encode(k: int, n: int, input_bytes: bytes) -> list[bytes]:
    """
    ZFEC-encode an input message according to the given parameters

    :param k: the number of shares required to recover the original
    :param n: the total number of shares
    :param input_bytes: the input message, in bytes

    :returns: n arrays of bytes, each one containing a single share
    """
    input_size = len(input_bytes)
    # note: the C++ code checks that input_bytes is a multiple of k
    outsize = input_size // k
    p_p_nbytes = c_char_p * n

    # allocate memory for the outputs (create_string_buffer makes bytes)
    outputs = [
        create_string_buffer(outsize)
        for a in range(n)
    ]

    c_outputs = p_p_nbytes(*[
        addressof(output)
        for output in outputs
    ])

    # actual C call
    _DLL.botan_zfec_encode(
        c_size_t(k), c_size_t(n), input_bytes, c_size_t(input_size), c_outputs
    )
    return [output.raw for output in outputs]


def zfec_decode(k: int, n: int, indexes: list[int], inputs: list[bytes]) -> list[bytes]:
    """
    ZFEC decode

    :param k: the number of shares required to recover the original
    :param n: the total number of shares
    :param indexes: which of the shares are we giving the decoder
    :param inputs: the input shares (e.g. from a previous call to zfec_encode) which all must be the same length

    Exactly ``k`` shares are needed to recover the data. Supplying more than ``k``
    (index, share) pairs is allowed; the extras are ignored.

    :returns: a list of bytes containing the original shares decoded from the provided shares (in ``inputs``)
    """

    # botan_zfec_decode() reads exactly K indexes and K input shares without
    # bounds checking, so validate and normalize the arrays here before handing
    # them to the native call to avoid out-of-bounds reads.
    if not 1 <= k <= n < 256:
        raise BotanException('Invalid zfec parameters: require 1 <= k <= n < 256')

    if len(indexes) != len(inputs):
        raise BotanException('zfec_decode requires one index per input share')

    if len(inputs) < k:
        raise BotanException('zfec_decode requires at least k input shares')

    if any(not 0 <= index < n for index in indexes):
        raise BotanException('zfec_decode index out of range [0, n)')

    if len(set(indexes)) != len(indexes):
        raise BotanException('zfec_decode indexes must be unique')

    # Only k shares are needed. If more are supplied, keep the k with the
    # lowest indexes and ignore the rest: indexes 0..k-1 are the systematic
    # shares, which reproduce the original data directly, so preferring the
    # lower indexes avoids the cost of decoding from parity shares.
    if len(inputs) > k:
        chosen = sorted(zip(indexes, inputs), key=lambda pair: pair[0])[:k]
        indexes = [index for index, _ in chosen]
        inputs = [share for _, share in chosen]

    p_size_t = c_size_t * len(indexes)
    c_indexes = p_size_t(*[c_size_t(index) for index in indexes])
    p_p_nbytes = c_char_p * n
    c_inputs = p_p_nbytes(*[c_char_p(inp) for inp in inputs])
    # all inputs must be the same length
    share_size = len(inputs[0])
    for i in inputs:
        if len(i) != share_size:
            raise ValueError(
                "Share size mismatch: {} != {}".format(len(i), share_size)
            )

    # allocate memory for our outputs (create_string_buffer creates
    # bytes)
    outputs = [
        create_string_buffer(share_size)
        for _ in range(k)
    ]
    p_p_kbytes = c_char_p * k
    c_outputs = p_p_kbytes(*[
        addressof(output)
        for output in outputs
    ])

    # actual C call
    _DLL.botan_zfec_decode(
        c_size_t(k), c_size_t(n), c_indexes, c_inputs, c_size_t(share_size), c_outputs
    )
    return [output.raw for output in outputs]
