diff --git a/.gitmodules b/.gitmodules index f8da97a..9851f45 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,7 +2,3 @@ path = SEAL url = https://github.com/microsoft/SEAL.git branch = main -[submodule "pybind11"] - path = pybind11 - url = https://github.com/pybind/pybind11.git - branch = stable diff --git a/README.md b/README.md index 0351427..e09db25 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,11 @@ ## Microsoft SEAL For Python +This is a python binding for the Microsoft SEAL library. + Microsoft [**SEAL**](https://github.com/microsoft/SEAL) is an easy-to-use open-source ([MIT licensed](https://github.com/microsoft/SEAL/blob/master/LICENSE)) homomorphic encryption library developed by the Cryptography Research group at Microsoft. [**pybind11**](https://github.com/pybind/pybind11) is a lightweight header-only library that exposes C++ types in Python and vice versa, mainly to create Python bindings of existing C++ code. -This is a python binding for the Microsoft SEAL library. - [![Supported Versions](https://img.shields.io/pypi/pyversions/SEAL-Python.svg)](https://github.com/Huelse/SEAL-Python) @@ -35,6 +35,16 @@ python -m pip install seal-python ## Build +On macOS, use the same deployment target for both the SEAL static library and the Python extension to avoid linker warnings about mismatched macOS versions: + +```shell +export MACOSX_DEPLOYMENT_TARGET=$(python3 - <<'PY' +import sysconfig +print(sysconfig.get_config_var("MACOSX_DEPLOYMENT_TARGET") or "15.0") +PY +) +``` + * ### Linux Recommend: Clang++ (>= 10.0) or GNU G++ (>= 9.4), CMake (>= 3.16) @@ -50,7 +60,7 @@ python -m pip install seal-python # Install dependencies pip3 install numpy pybind11 - # Init the SEAL and pybind11 + # Pull the SEAL git submodule update --init --recursive # Get the newest repositories (dev only) # git submodule update --remote @@ -58,6 +68,8 @@ python -m pip install seal-python # Build the SEAL lib without the msgsl zlib and zstandard compression cd SEAL cmake -S . -B build -DSEAL_USE_MSGSL=OFF -DSEAL_USE_ZLIB=OFF -DSEAL_USE_ZSTD=OFF + # macOS: + cmake -S . -B build -DSEAL_USE_MSGSL=OFF -DSEAL_USE_ZLIB=OFF -DSEAL_USE_ZSTD=OFF -DCMAKE_OSX_DEPLOYMENT_TARGET=${MACOSX_DEPLOYMENT_TARGET} cmake --build build cd .. @@ -81,8 +93,8 @@ python -m pip install seal-python ```shell # Run in "x64 Native Tools Command Prompt for VS" command prompt - cmake -S . -B build -G Ninja -DSEAL_USE_MSGSL=OFF -DSEAL_USE_ZLIB=OFF - cmake --build build + cmake -S . -B build -G Ninja -DSEAL_USE_MSGSL=OFF -DSEAL_USE_ZLIB=OFF -DSEAL_USE_ZSTD=OFF + cmake --build build --config Release # Build pip install numpy pybind11 diff --git a/pybind11 b/pybind11 deleted file mode 160000 index 04cfb45..0000000 --- a/pybind11 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 04cfb45b7998fe960a70743770b94bdb3a712f73 diff --git a/seal.pyi b/seal.pyi index 4925a77..b44b85d 100644 --- a/seal.pyi +++ b/seal.pyi @@ -60,59 +60,156 @@ class VectorInt(list[int]): ... class MemoryPoolHandle: - def __init__(self) -> None: ... + """Handle to a memory pool used by SEAL for temporary allocations.""" + + def __init__(self) -> None: + """Construct an uninitialized memory pool handle.""" + ... + @staticmethod - def Global() -> MemoryPoolHandle: ... + def Global() -> MemoryPoolHandle: + """Return a handle to the global memory pool.""" + ... + @staticmethod - def ThreadLocal() -> MemoryPoolHandle: ... + def ThreadLocal() -> MemoryPoolHandle: + """Return a handle to the thread-local memory pool.""" + ... + @staticmethod - def New(clear_on_destruction: bool = False) -> MemoryPoolHandle: ... - def pool_count(self) -> int: ... - def alloc_byte_count(self) -> int: ... - def use_count(self) -> int: ... - def is_initialized(self) -> bool: ... + def New(clear_on_destruction: bool = False) -> MemoryPoolHandle: + """Create a new independent memory pool.""" + ... + + def pool_count(self) -> int: + """Return the number of memory pools referenced by this handle.""" + ... + + def alloc_byte_count(self) -> int: + """Return the number of bytes allocated by the pool.""" + ... + + def use_count(self) -> int: + """Return the reference count of the underlying pool.""" + ... + + def is_initialized(self) -> bool: + """Return whether this handle points to an initialized pool.""" + ... class MemoryManager: @staticmethod - def GetPool() -> MemoryPoolHandle: ... + def GetPool() -> MemoryPoolHandle: + """Return the default memory pool handle.""" + ... class Modulus: - def __init__(self, value: int) -> None: ... - def bit_count(self) -> int: ... - def value(self) -> int: ... - def is_zero(self) -> bool: ... - def is_prime(self) -> bool: ... - def reduce(self, value: int) -> int: ... + """Represents an integer modulus used in encryption parameters.""" + + def __init__(self, value: int) -> None: + """Construct a modulus from an unsigned 64-bit integer.""" + ... + + def bit_count(self) -> int: + """Return the bit length of the modulus.""" + ... + + def value(self) -> int: + """Return the numeric value of the modulus.""" + ... + + def is_zero(self) -> bool: + """Return whether the modulus is zero.""" + ... + + def is_prime(self) -> bool: + """Return whether the modulus value is prime.""" + ... + + def reduce(self, value: int) -> int: + """Reduce an integer modulo this modulus.""" + ... class EncryptionParameters: + """User-configurable encryption settings for a SEAL scheme.""" + @overload - def __init__(self, scheme: scheme_type) -> None: ... + def __init__(self, scheme: scheme_type) -> None: + """Create an empty set of encryption parameters for the given scheme.""" + ... + @overload - def __init__(self, other: EncryptionParameters) -> None: ... - def set_poly_modulus_degree(self, poly_modulus_degree: int) -> None: ... - def set_coeff_modulus(self, coeff_modulus: Sequence[Modulus]) -> None: ... + def __init__(self, other: EncryptionParameters) -> None: + """Create a copy of an existing EncryptionParameters object.""" + ... + + def set_poly_modulus_degree(self, poly_modulus_degree: int) -> None: + """Set the degree of the polynomial modulus.""" + ... + + def set_coeff_modulus(self, coeff_modulus: Sequence[Modulus]) -> None: + """Set the coefficient modulus as a list of distinct prime moduli.""" + ... + @overload - def set_plain_modulus(self, plain_modulus: Modulus) -> None: ... + def set_plain_modulus(self, plain_modulus: Modulus) -> None: + """Set the plaintext modulus from a Modulus object.""" + ... + @overload - def set_plain_modulus(self, plain_modulus: int) -> None: ... - def scheme(self) -> scheme_type: ... - def poly_modulus_degree(self) -> int: ... - def coeff_modulus(self) -> Sequence[Modulus]: ... - def plain_modulus(self) -> Modulus: ... + def set_plain_modulus(self, plain_modulus: int) -> None: + """Set the plaintext modulus from an integer value.""" + ... + + def scheme(self) -> scheme_type: + """Return the selected encryption scheme.""" + ... + + def poly_modulus_degree(self) -> int: + """Return the degree of the polynomial modulus.""" + ... + + def coeff_modulus(self) -> Sequence[Modulus]: + """Return the coefficient modulus chain.""" + ... + + def plain_modulus(self) -> Modulus: + """Return the plaintext modulus.""" + ... + @overload - def save(self, path: str) -> None: ... + def save(self, path: str) -> None: + """Serialize the encryption parameters to a file.""" + ... + @overload - def save(self, path: str, compr_mode: compr_mode_type) -> None: ... - def load(self, path: str) -> None: ... - def load_bytes(self, data: bytes) -> None: ... - def save_size(self, compr_mode: compr_mode_type = ...) -> int: ... - def to_bytes(self, compr_mode: compr_mode_type = ...) -> bytes: ... + def save(self, path: str, compr_mode: compr_mode_type) -> None: + """Serialize the encryption parameters using the given compression mode.""" + ... + + def load(self, path: str) -> None: + """Load serialized encryption parameters from a file.""" + ... + + def load_bytes(self, data: bytes) -> None: + """Load serialized encryption parameters from bytes.""" + ... + + def save_size(self, compr_mode: compr_mode_type = ...) -> int: + """Return the serialized size in bytes for the given compression mode.""" + ... + + def to_bytes(self, compr_mode: compr_mode_type = ...) -> bytes: + """Serialize the encryption parameters to a Python bytes object.""" + ... class EncryptionParameterQualifiers: + """Precomputed validation results and capabilities of a parameter set.""" + parameter_error: error_type using_fft: bool using_ntt: bool @@ -120,351 +217,976 @@ class EncryptionParameterQualifiers: using_fast_plain_lift: bool using_descending_modulus_chain: bool sec_level: sec_level_type - def parameters_set(self) -> bool: ... - def parameter_error_name(self) -> str: ... - def parameter_error_message(self) -> str: ... + + def parameters_set(self) -> bool: + """Return whether the parameters were validated successfully.""" + ... + + def parameter_error_name(self) -> str: + """Return the symbolic name of the validation error.""" + ... + + def parameter_error_message(self) -> str: + """Return a human-readable explanation of the validation error.""" + ... class ContextData: - def parms(self) -> EncryptionParameters: ... - def parms_id(self) -> ParmsId: ... - def qualifiers(self) -> EncryptionParameterQualifiers: ... - def total_coeff_modulus(self) -> int: ... - def total_coeff_modulus_bit_count(self) -> int: ... - def next_context_data(self) -> ContextData | None: ... - def chain_index(self) -> int: ... + """Precomputation data for one level in the modulus switching chain.""" + + def parms(self) -> EncryptionParameters: + """Return the encryption parameters for this context level.""" + ... + + def parms_id(self) -> ParmsId: + """Return the unique parameter identifier for this level.""" + ... + + def qualifiers(self) -> EncryptionParameterQualifiers: + """Return qualifiers derived from these parameters.""" + ... + + def total_coeff_modulus(self) -> int: + """Return the product of all coefficient modulus primes.""" + ... + + def total_coeff_modulus_bit_count(self) -> int: + """Return the bit count of the total coefficient modulus.""" + ... + + def next_context_data(self) -> ContextData | None: + """Return the next lower level in the modulus switching chain.""" + ... + + def chain_index(self) -> int: + """Return the chain index for this context level.""" + ... class SEALContext: + """Validates parameters and stores heavy-weight SEAL precomputations.""" + def __init__( self, parms: EncryptionParameters, expand_mod_chain: bool = True, sec_level: sec_level_type = sec_level_type.tc128, - ) -> None: ... - def get_context_data(self, parms_id: ParmsId) -> ContextData | None: ... - def key_context_data(self) -> ContextData: ... - def first_context_data(self) -> ContextData: ... - def last_context_data(self) -> ContextData: ... - def parameters_set(self) -> bool: ... - def parameter_error_name(self) -> str: ... - def parameter_error_message(self) -> str: ... - def first_parms_id(self) -> ParmsId: ... - def last_parms_id(self) -> ParmsId: ... - def using_keyswitching(self) -> bool: ... - def from_cipher_str(self, data: bytes | str) -> Ciphertext: ... - def from_plain_str(self, data: bytes | str) -> Plaintext: ... - def from_secret_str(self, data: bytes | str) -> SecretKey: ... - def from_public_str(self, data: bytes | str) -> PublicKey: ... - def from_relin_str(self, data: bytes | str) -> RelinKeys: ... - def from_galois_str(self, data: bytes | str) -> GaloisKeys: ... + ) -> None: + """Create a context and optionally expand the modulus switching chain.""" + ... + + def get_context_data(self, parms_id: ParmsId) -> ContextData | None: + """Return ContextData for a specific parms_id.""" + ... + + def key_context_data(self) -> ContextData: + """Return the key-level ContextData.""" + ... + + def first_context_data(self) -> ContextData: + """Return the first data-level ContextData in the chain.""" + ... + + def last_context_data(self) -> ContextData: + """Return the last valid ContextData in the chain.""" + ... + + def parameters_set(self) -> bool: + """Return whether the parameters were validated successfully.""" + ... + + def parameter_error_name(self) -> str: + """Return the symbolic name of the validation result.""" + ... + + def parameter_error_message(self) -> str: + """Return a human-readable validation message.""" + ... + + def first_parms_id(self) -> ParmsId: + """Return the parms_id for the first data-level parameters.""" + ... + + def last_parms_id(self) -> ParmsId: + """Return the parms_id for the last valid parameters in the chain.""" + ... + + def using_keyswitching(self) -> bool: + """Return whether the parameter chain supports key switching.""" + ... + + def from_cipher_str(self, data: bytes | str) -> Ciphertext: + """Deserialize a Ciphertext from serialized bytes.""" + ... + + def from_plain_str(self, data: bytes | str) -> Plaintext: + """Deserialize a Plaintext from serialized bytes.""" + ... + + def from_secret_str(self, data: bytes | str) -> SecretKey: + """Deserialize a SecretKey from serialized bytes.""" + ... + + def from_public_str(self, data: bytes | str) -> PublicKey: + """Deserialize a PublicKey from serialized bytes.""" + ... + + def from_relin_str(self, data: bytes | str) -> RelinKeys: + """Deserialize RelinKeys from serialized bytes.""" + ... + + def from_galois_str(self, data: bytes | str) -> GaloisKeys: + """Deserialize GaloisKeys from serialized bytes.""" + ... class CoeffModulus: + """Factory helpers for constructing coefficient modulus chains.""" + @staticmethod - def MaxBitCount(poly_modulus_degree: int, sec_level: sec_level_type = sec_level_type.tc128) -> int: ... + def MaxBitCount(poly_modulus_degree: int, sec_level: sec_level_type = sec_level_type.tc128) -> int: + """Return the maximum safe total bit count for the coefficient modulus.""" + ... + @staticmethod - def BFVDefault(poly_modulus_degree: int, sec_level: sec_level_type = sec_level_type.tc128) -> Sequence[Modulus]: ... + def BFVDefault(poly_modulus_degree: int, sec_level: sec_level_type = sec_level_type.tc128) -> Sequence[Modulus]: + """Return SEAL's default BFV/BGV coefficient modulus.""" + ... + @overload @staticmethod - def Create(poly_modulus_degree: int, bit_sizes: Sequence[int]) -> Sequence[Modulus]: ... + def Create(poly_modulus_degree: int, bit_sizes: Sequence[int]) -> Sequence[Modulus]: + """Create a coefficient modulus chain with primes of the given bit sizes.""" + ... + @overload @staticmethod - def Create(poly_modulus_degree: int, plain_modulus: Modulus, bit_sizes: Sequence[int]) -> Sequence[Modulus]: ... + def Create(poly_modulus_degree: int, plain_modulus: Modulus, bit_sizes: Sequence[int]) -> Sequence[Modulus]: + """Create a coefficient modulus chain tailored to a plaintext modulus.""" + ... class PlainModulus: + """Factory helpers for constructing plaintext moduli.""" + @overload @staticmethod - def Batching(poly_modulus_degree: int, bit_size: int) -> Modulus: ... + def Batching(poly_modulus_degree: int, bit_size: int) -> Modulus: + """Create one batching-compatible plaintext modulus.""" + ... + @overload @staticmethod - def Batching(poly_modulus_degree: int, bit_sizes: Sequence[int]) -> Sequence[Modulus]: ... + def Batching(poly_modulus_degree: int, bit_sizes: Sequence[int]) -> Sequence[Modulus]: + """Create batching-compatible plaintext moduli for the given bit sizes.""" + ... class Plaintext: + """Plaintext polynomial container used by BFV/BGV and CKKS.""" + @overload - def __init__(self) -> None: ... + def __init__(self) -> None: + """Construct an empty plaintext with no allocated data.""" + ... + @overload - def __init__(self, coeff_count: int) -> None: ... + def __init__(self, coeff_count: int) -> None: + """Construct a zero plaintext with the given coefficient count.""" + ... + @overload - def __init__(self, coeff_count: int, capacity: int) -> None: ... + def __init__(self, coeff_count: int, capacity: int) -> None: + """Construct a zero plaintext with explicit coefficient count and capacity.""" + ... + @overload - def __init__(self, hex_poly: str) -> None: ... + def __init__(self, hex_poly: str) -> None: + """Construct a plaintext from its hexadecimal polynomial string form.""" + ... + @overload - def __init__(self, other: Plaintext) -> None: ... + def __init__(self, other: Plaintext) -> None: + """Construct a copy of an existing plaintext.""" + ... + @overload - def set_zero(self) -> None: ... + def set_zero(self) -> None: + """Set all coefficients to zero.""" + ... + @overload - def set_zero(self, start_coeff: int) -> None: ... + def set_zero(self, start_coeff: int) -> None: + """Set coefficients from start_coeff to the end to zero.""" + ... + @overload - def set_zero(self, start_coeff: int, length: int) -> None: ... - def is_zero(self) -> bool: ... - def capacity(self) -> int: ... - def coeff_count(self) -> int: ... - def significant_coeff_count(self) -> int: ... - def nonzero_coeff_count(self) -> int: ... - def to_string(self) -> str: ... - def is_ntt_form(self) -> bool: ... - def parms_id(self) -> ParmsId: ... + def set_zero(self, start_coeff: int, length: int) -> None: + """Set a range of coefficients to zero.""" + ... + + def is_zero(self) -> bool: + """Return whether all coefficients are zero.""" + ... + + def capacity(self) -> int: + """Return the allocation capacity measured in coefficients.""" + ... + + def coeff_count(self) -> int: + """Return the number of coefficients stored in the plaintext.""" + ... + + def significant_coeff_count(self) -> int: + """Return the number of significant coefficients.""" + ... + + def nonzero_coeff_count(self) -> int: + """Return the number of non-zero coefficients.""" + ... + + def to_string(self) -> str: + """Return the plaintext polynomial formatted as a hexadecimal string.""" + ... + + def is_ntt_form(self) -> bool: + """Return whether the plaintext is stored in NTT form.""" + ... + + def parms_id(self) -> ParmsId: + """Return the parms_id associated with this plaintext.""" + ... + @overload - def scale(self) -> float: ... + def scale(self) -> float: + """Return the CKKS scale attached to this plaintext.""" + ... + @overload - def scale(self, value: float) -> None: ... + def scale(self, value: float) -> None: + """Set the CKKS scale attached to this plaintext.""" + ... + @overload - def save(self, path: str) -> None: ... + def save(self, path: str) -> None: + """Serialize the plaintext to a file.""" + ... + @overload - def save(self, path: str, compr_mode: compr_mode_type) -> None: ... - def load(self, context: SEALContext, path: str) -> None: ... - def load_bytes(self, context: SEALContext, data: bytes) -> None: ... - def save_size(self, compr_mode: compr_mode_type = ...) -> int: ... - def to_bytes(self, compr_mode: compr_mode_type = ...) -> bytes: ... + def save(self, path: str, compr_mode: compr_mode_type) -> None: + """Serialize the plaintext using the given compression mode.""" + ... + + def load(self, context: SEALContext, path: str) -> None: + """Load a serialized plaintext from a file and validate it.""" + ... + + def load_bytes(self, context: SEALContext, data: bytes) -> None: + """Load a serialized plaintext from bytes and validate it.""" + ... + + def save_size(self, compr_mode: compr_mode_type = ...) -> int: + """Return the serialized size in bytes for the given compression mode.""" + ... + + def to_bytes(self, compr_mode: compr_mode_type = ...) -> bytes: + """Serialize the plaintext to a Python bytes object.""" + ... class Ciphertext: + """Encrypted value together with parameter metadata.""" + @overload - def __init__(self) -> None: ... + def __init__(self) -> None: + """Construct an empty ciphertext with no allocated data.""" + ... + @overload - def __init__(self, context: SEALContext) -> None: ... + def __init__(self, context: SEALContext) -> None: + """Construct an empty ciphertext at the highest data level.""" + ... + @overload - def __init__(self, context: SEALContext, parms_id: ParmsId) -> None: ... + def __init__(self, context: SEALContext, parms_id: ParmsId) -> None: + """Construct an empty ciphertext initialized for a specific parms_id.""" + ... + @overload - def __init__(self, context: SEALContext, parms_id: ParmsId, size_capacity: int) -> None: ... + def __init__(self, context: SEALContext, parms_id: ParmsId, size_capacity: int) -> None: + """Construct an empty ciphertext with explicit polynomial capacity.""" + ... + @overload - def __init__(self, other: Ciphertext) -> None: ... - def coeff_modulus_size(self) -> int: ... - def poly_modulus_degree(self) -> int: ... - def size(self) -> int: ... - def size_capacity(self) -> int: ... - def is_transparent(self) -> bool: ... - def is_ntt_form(self) -> bool: ... - def parms_id(self) -> ParmsId: ... + def __init__(self, other: Ciphertext) -> None: + """Construct a copy of an existing ciphertext.""" + ... + + def coeff_modulus_size(self) -> int: + """Return the number of coefficient modulus primes.""" + ... + + def poly_modulus_degree(self) -> int: + """Return the polynomial modulus degree.""" + ... + + def size(self) -> int: + """Return the number of polynomials in the ciphertext.""" + ... + + def size_capacity(self) -> int: + """Return the allocated ciphertext capacity.""" + ... + + def is_transparent(self) -> bool: + """Return whether the ciphertext is transparent.""" + ... + + def is_ntt_form(self) -> bool: + """Return whether the ciphertext is stored in NTT form.""" + ... + + def parms_id(self) -> ParmsId: + """Return the parms_id associated with this ciphertext.""" + ... + @overload - def scale(self) -> float: ... + def scale(self) -> float: + """Return the CKKS scale attached to this ciphertext.""" + ... + @overload - def scale(self, value: float) -> None: ... + def scale(self, value: float) -> None: + """Set the CKKS scale attached to this ciphertext.""" + ... + @overload - def save(self, path: str) -> None: ... + def save(self, path: str) -> None: + """Serialize the ciphertext to a file.""" + ... + @overload - def save(self, path: str, compr_mode: compr_mode_type) -> None: ... - def load(self, context: SEALContext, path: str) -> None: ... - def load_bytes(self, context: SEALContext, data: bytes) -> None: ... - def save_size(self, compr_mode: compr_mode_type = ...) -> int: ... - def to_string(self, compr_mode: compr_mode_type = ...) -> bytes: ... + def save(self, path: str, compr_mode: compr_mode_type) -> None: + """Serialize the ciphertext using the given compression mode.""" + ... + + def load(self, context: SEALContext, path: str) -> None: + """Load a serialized ciphertext from a file and validate it.""" + ... + + def load_bytes(self, context: SEALContext, data: bytes) -> None: + """Load a serialized ciphertext from bytes and validate it.""" + ... + + def save_size(self, compr_mode: compr_mode_type = ...) -> int: + """Return the serialized size in bytes for the given compression mode.""" + ... + + def to_string(self, compr_mode: compr_mode_type = ...) -> bytes: + """Serialize the ciphertext to a Python bytes object.""" + ... class SecretKey: + """Stores the secret key used for decryption and symmetric encryption.""" + @overload - def __init__(self) -> None: ... + def __init__(self) -> None: + """Construct an empty secret key.""" + ... + @overload - def __init__(self, other: SecretKey) -> None: ... - def parms_id(self) -> ParmsId: ... - def save(self, path: str) -> None: ... - def load(self, context: SEALContext, path: str) -> None: ... - def to_string(self) -> bytes: ... + def __init__(self, other: SecretKey) -> None: + """Construct a copy of an existing secret key.""" + ... + + def parms_id(self) -> ParmsId: + """Return the parms_id associated with the secret key.""" + ... + + def save(self, path: str) -> None: + """Serialize the secret key to a file.""" + ... + + def load(self, context: SEALContext, path: str) -> None: + """Load a serialized secret key from a file.""" + ... + + def to_string(self) -> bytes: + """Serialize the secret key to a Python bytes object.""" + ... class PublicKey: + """Stores the public key used for public-key encryption.""" + @overload - def __init__(self) -> None: ... + def __init__(self) -> None: + """Construct an empty public key.""" + ... + @overload - def __init__(self, other: PublicKey) -> None: ... - def parms_id(self) -> ParmsId: ... - def save(self, path: str) -> None: ... - def load(self, context: SEALContext, path: str) -> None: ... - def to_string(self) -> bytes: ... + def __init__(self, other: PublicKey) -> None: + """Construct a copy of an existing public key.""" + ... + + def parms_id(self) -> ParmsId: + """Return the parms_id associated with the public key.""" + ... + + def save(self, path: str) -> None: + """Serialize the public key to a file.""" + ... + + def load(self, context: SEALContext, path: str) -> None: + """Load a serialized public key from a file.""" + ... + + def to_string(self) -> bytes: + """Serialize the public key to a Python bytes object.""" + ... class KSwitchKeys: + """Base container for key switching key material.""" + @overload - def __init__(self) -> None: ... + def __init__(self) -> None: + """Construct an empty key switching key container.""" + ... + @overload - def __init__(self, other: KSwitchKeys) -> None: ... - def size(self) -> int: ... - def parms_id(self) -> ParmsId: ... - def save(self, path: str) -> None: ... - def load(self, context: SEALContext, path: str) -> None: ... + def __init__(self, other: KSwitchKeys) -> None: + """Construct a copy of an existing key switching key container.""" + ... + + def size(self) -> int: + """Return the number of stored key switching key sets.""" + ... + + def parms_id(self) -> ParmsId: + """Return the parms_id associated with this key set.""" + ... + + def save(self, path: str) -> None: + """Serialize the key switching keys to a file.""" + ... + + def load(self, context: SEALContext, path: str) -> None: + """Load serialized key switching keys from a file.""" + ... class RelinKeys(KSwitchKeys): + """Relinearization keys used to shrink ciphertext size after multiplication.""" + @overload - def __init__(self) -> None: ... + def __init__(self) -> None: + """Construct an empty set of relinearization keys.""" + ... + @overload - def __init__(self, other: KSwitchKeys) -> None: ... + def __init__(self, other: KSwitchKeys) -> None: + """Construct relinearization keys from a key switching key base object.""" + ... + @staticmethod - def get_index(key_power: int) -> int: ... - def has_key(self, key_power: int) -> bool: ... - def to_string(self) -> bytes: ... + def get_index(key_power: int) -> int: + """Map a key power to SEAL's internal storage index.""" + ... + + def has_key(self, key_power: int) -> bool: + """Return whether a relinearization key exists for key_power.""" + ... + + def to_string(self) -> bytes: + """Serialize the relinearization keys to a Python bytes object.""" + ... class GaloisKeys(KSwitchKeys): + """Galois keys used for rotations and CKKS complex conjugation.""" + @overload - def __init__(self) -> None: ... + def __init__(self) -> None: + """Construct an empty set of Galois keys.""" + ... + @overload - def __init__(self, other: KSwitchKeys) -> None: ... + def __init__(self, other: KSwitchKeys) -> None: + """Construct Galois keys from a key switching key base object.""" + ... + @staticmethod - def get_index(galois_elt: int) -> int: ... - def has_key(self, galois_elt: int) -> bool: ... - def to_string(self) -> bytes: ... + def get_index(galois_elt: int) -> int: + """Map a Galois element to SEAL's internal storage index.""" + ... + + def has_key(self, galois_elt: int) -> bool: + """Return whether a Galois key exists for galois_elt.""" + ... + + def to_string(self) -> bytes: + """Serialize the Galois keys to a Python bytes object.""" + ... class KeyGenerator: + """Generate secret, public, relinearization, and Galois keys.""" + @overload - def __init__(self, context: SEALContext) -> None: ... + def __init__(self, context: SEALContext) -> None: + """Create a key generator and generate a fresh secret key.""" + ... + @overload - def __init__(self, context: SEALContext, secret_key: SecretKey) -> None: ... - def secret_key(self) -> SecretKey: ... + def __init__(self, context: SEALContext, secret_key: SecretKey) -> None: + """Create a key generator from an existing secret key.""" + ... + + def secret_key(self) -> SecretKey: + """Return the secret key managed by this generator.""" + ... + @overload - def create_public_key(self) -> PublicKey: ... + def create_public_key(self) -> PublicKey: + """Generate and return a new public key.""" + ... + @overload - def create_public_key(self, destination: PublicKey) -> None: ... + def create_public_key(self, destination: PublicKey) -> None: + """Generate a public key and store it in destination.""" + ... + @overload - def create_relin_keys(self) -> RelinKeys: ... + def create_relin_keys(self) -> RelinKeys: + """Generate and return relinearization keys.""" + ... + @overload - def create_relin_keys(self, destination: RelinKeys) -> None: ... + def create_relin_keys(self, destination: RelinKeys) -> None: + """Generate relinearization keys and store them in destination.""" + ... + @overload - def create_galois_keys(self) -> GaloisKeys: ... + def create_galois_keys(self) -> GaloisKeys: + """Generate and return all supported Galois keys.""" + ... + @overload - def create_galois_keys(self, destination: GaloisKeys) -> None: ... + def create_galois_keys(self, destination: GaloisKeys) -> None: + """Generate all supported Galois keys and store them in destination.""" + ... + @overload - def create_galois_keys(self, galois_elts: Sequence[int], destination: GaloisKeys) -> None: ... + def create_galois_keys(self, galois_elts: Sequence[int], destination: GaloisKeys) -> None: + """Generate Galois keys for the requested rotation steps.""" + ... class Encryptor: + """Encrypt plaintexts using a public key or a secret key.""" + @overload - def __init__(self, context: SEALContext, public_key: PublicKey) -> None: ... + def __init__(self, context: SEALContext, public_key: PublicKey) -> None: + """Create an encryptor configured for public-key encryption.""" + ... + @overload - def __init__(self, context: SEALContext, secret_key: SecretKey) -> None: ... + def __init__(self, context: SEALContext, secret_key: SecretKey) -> None: + """Create an encryptor configured for secret-key encryption.""" + ... + @overload - def __init__(self, context: SEALContext, public_key: PublicKey, secret_key: SecretKey) -> None: ... - def set_public_key(self, public_key: PublicKey) -> None: ... - def set_secret_key(self, secret_key: SecretKey) -> None: ... + def __init__(self, context: SEALContext, public_key: PublicKey, secret_key: SecretKey) -> None: + """Create an encryptor configured with both public and secret keys.""" + ... + + def set_public_key(self, public_key: PublicKey) -> None: + """Set or replace the public key used for encryption.""" + ... + + def set_secret_key(self, secret_key: SecretKey) -> None: + """Set or replace the secret key used for symmetric encryption.""" + ... + @overload - def encrypt_zero(self) -> Ciphertext: ... + def encrypt_zero(self) -> Ciphertext: + """Encrypt zero at the first data level and return the ciphertext.""" + ... + @overload - def encrypt_zero(self, destination: Ciphertext) -> None: ... + def encrypt_zero(self, destination: Ciphertext) -> None: + """Encrypt zero at the first data level into destination.""" + ... + @overload - def encrypt_zero(self, parms_id: ParmsId) -> Ciphertext: ... + def encrypt_zero(self, parms_id: ParmsId) -> Ciphertext: + """Encrypt zero for the specified parms_id and return the ciphertext.""" + ... + @overload - def encrypt_zero(self, parms_id: ParmsId, destination: Ciphertext) -> None: ... + def encrypt_zero(self, parms_id: ParmsId, destination: Ciphertext) -> None: + """Encrypt zero for the specified parms_id into destination.""" + ... + @overload - def encrypt(self, plain: Plaintext) -> Ciphertext: ... + def encrypt(self, plain: Plaintext) -> Ciphertext: + """Encrypt a plaintext with the public key and return the ciphertext.""" + ... + @overload - def encrypt(self, plain: Plaintext, destination: Ciphertext) -> None: ... + def encrypt(self, plain: Plaintext, destination: Ciphertext) -> None: + """Encrypt a plaintext with the public key into destination.""" + ... + @overload - def encrypt_symmetric(self, plain: Plaintext) -> Ciphertext: ... + def encrypt_symmetric(self, plain: Plaintext) -> Ciphertext: + """Encrypt a plaintext with the secret key and return the ciphertext.""" + ... + @overload - def encrypt_symmetric(self, plain: Plaintext, destination: Ciphertext) -> None: ... + def encrypt_symmetric(self, plain: Plaintext, destination: Ciphertext) -> None: + """Encrypt a plaintext with the secret key into destination.""" + ... class Evaluator: - def __init__(self, context: SEALContext) -> None: ... - def negate_inplace(self, encrypted: Ciphertext) -> None: ... - def negate(self, encrypted1: Ciphertext) -> Ciphertext: ... - def add_inplace(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> None: ... - def add(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> Ciphertext: ... - def add_many(self, encrypteds: Sequence[Ciphertext]) -> Ciphertext: ... - def sub_inplace(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> None: ... - def sub(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> Ciphertext: ... - def multiply_inplace(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> None: ... - def multiply(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> Ciphertext: ... - def square_inplace(self, encrypted1: Ciphertext) -> None: ... - def square(self, encrypted1: Ciphertext) -> Ciphertext: ... - def relinearize_inplace(self, encrypted1: Ciphertext, relin_keys: RelinKeys) -> None: ... - def relinearize(self, encrypted1: Ciphertext, relin_keys: RelinKeys) -> Ciphertext: ... - @overload - def mod_switch_to_next(self, encrypted: Ciphertext) -> Ciphertext: ... - @overload - def mod_switch_to_next(self, plain: Plaintext) -> Plaintext: ... - @overload - def mod_switch_to_next_inplace(self, encrypted: Ciphertext) -> None: ... - @overload - def mod_switch_to_next_inplace(self, plain: Plaintext) -> None: ... - @overload - def mod_switch_to_inplace(self, encrypted: Ciphertext, parms_id: ParmsId) -> None: ... - @overload - def mod_switch_to_inplace(self, plain: Plaintext, parms_id: ParmsId) -> None: ... - @overload - def mod_switch_to(self, encrypted: Ciphertext, parms_id: ParmsId) -> Ciphertext: ... - @overload - def mod_switch_to(self, plain: Plaintext, parms_id: ParmsId) -> Plaintext: ... - def rescale_to_next(self, encrypted: Ciphertext) -> Ciphertext: ... - def rescale_to_next_inplace(self, encrypted: Ciphertext) -> None: ... - def rescale_to_inplace(self, encrypted: Ciphertext, parms_id: ParmsId) -> None: ... - def rescale_to(self, encrypted: Ciphertext, parms_id: ParmsId) -> Ciphertext: ... - def multiply_many(self, encrypteds: Sequence[Ciphertext], relin_keys: RelinKeys) -> Ciphertext: ... - def exponentiate_inplace(self, encrypted: Ciphertext, exponent: int, relin_keys: RelinKeys) -> None: ... - def exponentiate(self, encrypted: Ciphertext, exponent: int, relin_keys: RelinKeys) -> Ciphertext: ... - def add_plain_inplace(self, encrypted: Ciphertext, plain: Plaintext) -> None: ... - def add_plain(self, encrypted: Ciphertext, plain: Plaintext) -> Ciphertext: ... - def sub_plain_inplace(self, encrypted: Ciphertext, plain: Plaintext) -> None: ... - def sub_plain(self, encrypted: Ciphertext, plain: Plaintext) -> Ciphertext: ... - def multiply_plain_inplace(self, encrypted: Ciphertext, plain: Plaintext) -> None: ... - def multiply_plain(self, encrypted: Ciphertext, plain: Plaintext) -> Ciphertext: ... - @overload - def transform_to_ntt_inplace(self, plain: Plaintext, parms_id: ParmsId) -> None: ... - @overload - def transform_to_ntt_inplace(self, encrypted: Ciphertext) -> None: ... - @overload - def transform_to_ntt(self, plain: Plaintext, parms_id: ParmsId) -> Plaintext: ... - @overload - def transform_to_ntt(self, encrypted: Ciphertext) -> Ciphertext: ... - def transform_from_ntt_inplace(self, encrypted: Ciphertext) -> None: ... - def transform_from_ntt(self, encrypted_ntt: Ciphertext) -> Ciphertext: ... - def apply_galois_inplace(self, encrypted: Ciphertext, galois_elt: int, galois_keys: GaloisKeys) -> None: ... - def apply_galois(self, encrypted: Ciphertext, galois_elt: int, galois_keys: GaloisKeys) -> Ciphertext: ... - def rotate_rows_inplace(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> None: ... - def rotate_rows(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> Ciphertext: ... - def rotate_columns_inplace(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> None: ... - def rotate_columns(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> Ciphertext: ... - def rotate_vector_inplace(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> None: ... - def rotate_vector(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> Ciphertext: ... - def complex_conjugate_inplace(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> None: ... - def complex_conjugate(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> Ciphertext: ... + """Apply homomorphic operations to ciphertexts and plaintexts.""" + + def __init__(self, context: SEALContext) -> None: + """Create an evaluator for the given context.""" + ... + + def negate_inplace(self, encrypted: Ciphertext) -> None: + """Negate a ciphertext in place.""" + ... + + def negate(self, encrypted1: Ciphertext) -> Ciphertext: + """Negate a ciphertext and return the result.""" + ... + + def add_inplace(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> None: + """Add two ciphertexts and store the result in encrypted1.""" + ... + + def add(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> Ciphertext: + """Add two ciphertexts and return the result.""" + ... + + def add_many(self, encrypteds: Sequence[Ciphertext]) -> Ciphertext: + """Add many ciphertexts together and return the sum.""" + ... + + def sub_inplace(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> None: + """Subtract encrypted2 from encrypted1 in place.""" + ... + + def sub(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> Ciphertext: + """Subtract two ciphertexts and return the result.""" + ... + + def multiply_inplace(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> None: + """Multiply two ciphertexts and store the result in encrypted1.""" + ... + + def multiply(self, encrypted1: Ciphertext, encrypted2: Ciphertext) -> Ciphertext: + """Multiply two ciphertexts and return the result.""" + ... + + def square_inplace(self, encrypted1: Ciphertext) -> None: + """Square a ciphertext in place.""" + ... + + def square(self, encrypted1: Ciphertext) -> Ciphertext: + """Square a ciphertext and return the result.""" + ... + + def relinearize_inplace(self, encrypted1: Ciphertext, relin_keys: RelinKeys) -> None: + """Relinearize a ciphertext in place using relinearization keys.""" + ... + + def relinearize(self, encrypted1: Ciphertext, relin_keys: RelinKeys) -> Ciphertext: + """Relinearize a ciphertext and return the result.""" + ... + @overload + def mod_switch_to_next(self, encrypted: Ciphertext) -> Ciphertext: + """Mod-switch a ciphertext to the next level and return the result.""" + ... + + @overload + def mod_switch_to_next(self, plain: Plaintext) -> Plaintext: + """Mod-switch a plaintext to the next level and return the result.""" + ... + + @overload + def mod_switch_to_next_inplace(self, encrypted: Ciphertext) -> None: + """Mod-switch a ciphertext to the next level in place.""" + ... + + @overload + def mod_switch_to_next_inplace(self, plain: Plaintext) -> None: + """Mod-switch a plaintext to the next level in place.""" + ... + + @overload + def mod_switch_to_inplace(self, encrypted: Ciphertext, parms_id: ParmsId) -> None: + """Mod-switch a ciphertext in place to the specified parms_id.""" + ... + + @overload + def mod_switch_to_inplace(self, plain: Plaintext, parms_id: ParmsId) -> None: + """Mod-switch a plaintext in place to the specified parms_id.""" + ... + + @overload + def mod_switch_to(self, encrypted: Ciphertext, parms_id: ParmsId) -> Ciphertext: + """Mod-switch a ciphertext to the specified parms_id and return it.""" + ... + + @overload + def mod_switch_to(self, plain: Plaintext, parms_id: ParmsId) -> Plaintext: + """Mod-switch a plaintext to the specified parms_id and return it.""" + ... + + def rescale_to_next(self, encrypted: Ciphertext) -> Ciphertext: + """Rescale a CKKS ciphertext to the next level and return the result.""" + ... + + def rescale_to_next_inplace(self, encrypted: Ciphertext) -> None: + """Rescale a CKKS ciphertext to the next level in place.""" + ... + + def rescale_to_inplace(self, encrypted: Ciphertext, parms_id: ParmsId) -> None: + """Rescale a CKKS ciphertext in place to the specified parms_id.""" + ... + + def rescale_to(self, encrypted: Ciphertext, parms_id: ParmsId) -> Ciphertext: + """Rescale a CKKS ciphertext to the specified parms_id and return it.""" + ... + + def multiply_many(self, encrypteds: Sequence[Ciphertext], relin_keys: RelinKeys) -> Ciphertext: + """Multiply many ciphertexts together and return the result.""" + ... + + def exponentiate_inplace(self, encrypted: Ciphertext, exponent: int, relin_keys: RelinKeys) -> None: + """Raise a ciphertext to a power in place.""" + ... + + def exponentiate(self, encrypted: Ciphertext, exponent: int, relin_keys: RelinKeys) -> Ciphertext: + """Raise a ciphertext to a power and return the result.""" + ... + + def add_plain_inplace(self, encrypted: Ciphertext, plain: Plaintext) -> None: + """Add a plaintext to a ciphertext in place.""" + ... + + def add_plain(self, encrypted: Ciphertext, plain: Plaintext) -> Ciphertext: + """Add a plaintext to a ciphertext and return the result.""" + ... + + def sub_plain_inplace(self, encrypted: Ciphertext, plain: Plaintext) -> None: + """Subtract a plaintext from a ciphertext in place.""" + ... + + def sub_plain(self, encrypted: Ciphertext, plain: Plaintext) -> Ciphertext: + """Subtract a plaintext from a ciphertext and return the result.""" + ... + + def multiply_plain_inplace(self, encrypted: Ciphertext, plain: Plaintext) -> None: + """Multiply a ciphertext by a plaintext in place.""" + ... + + def multiply_plain(self, encrypted: Ciphertext, plain: Plaintext) -> Ciphertext: + """Multiply a ciphertext by a plaintext and return the result.""" + ... + + @overload + def transform_to_ntt_inplace(self, plain: Plaintext, parms_id: ParmsId) -> None: + """Transform a plaintext to NTT form in place.""" + ... + + @overload + def transform_to_ntt_inplace(self, encrypted: Ciphertext) -> None: + """Transform a ciphertext to NTT form in place.""" + ... + + @overload + def transform_to_ntt(self, plain: Plaintext, parms_id: ParmsId) -> Plaintext: + """Transform a plaintext to NTT form and return the result.""" + ... + + @overload + def transform_to_ntt(self, encrypted: Ciphertext) -> Ciphertext: + """Transform a ciphertext to NTT form and return the result.""" + ... + + def transform_from_ntt_inplace(self, encrypted: Ciphertext) -> None: + """Transform an NTT-form ciphertext back to coefficient form in place.""" + ... + + def transform_from_ntt(self, encrypted_ntt: Ciphertext) -> Ciphertext: + """Transform an NTT-form ciphertext back to coefficient form.""" + ... + + def apply_galois_inplace(self, encrypted: Ciphertext, galois_elt: int, galois_keys: GaloisKeys) -> None: + """Apply a Galois automorphism to a ciphertext in place.""" + ... + + def apply_galois(self, encrypted: Ciphertext, galois_elt: int, galois_keys: GaloisKeys) -> Ciphertext: + """Apply a Galois automorphism to a ciphertext and return the result.""" + ... + + def rotate_rows_inplace(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> None: + """Rotate BFV/BGV batching rows in place.""" + ... + + def rotate_rows(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> Ciphertext: + """Rotate BFV/BGV batching rows and return the result.""" + ... + + def rotate_columns_inplace(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> None: + """Rotate BFV/BGV batching columns in place.""" + ... + + def rotate_columns(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> Ciphertext: + """Rotate BFV/BGV batching columns and return the result.""" + ... + + def rotate_vector_inplace(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> None: + """Rotate a CKKS vector in place.""" + ... + + def rotate_vector(self, encrypted: Ciphertext, steps: int, galois_keys: GaloisKeys) -> Ciphertext: + """Rotate a CKKS vector and return the result.""" + ... + + def complex_conjugate_inplace(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> None: + """Apply CKKS complex conjugation in place.""" + ... + + def complex_conjugate(self, encrypted: Ciphertext, galois_keys: GaloisKeys) -> Ciphertext: + """Apply CKKS complex conjugation and return the result.""" + ... class CKKSEncoder: - def __init__(self, context: SEALContext) -> None: ... - def slot_count(self) -> int: ... + """Encode floating-point and complex vectors into CKKS plaintexts.""" + + def __init__(self, context: SEALContext) -> None: + """Create a CKKS encoder for the given context.""" + ... + + def slot_count(self) -> int: + """Return the number of SIMD slots available for CKKS encoding.""" + ... + @overload - def encode(self, values: FloatLikeArray, scale: float) -> Plaintext: ... + def encode(self, values: FloatLikeArray, scale: float) -> Plaintext: + """Encode real values and return the plaintext.""" + ... + @overload - def encode(self, values: FloatLikeArray, scale: float, destination: Plaintext) -> None: ... + def encode(self, values: FloatLikeArray, scale: float, destination: Plaintext) -> None: + """Encode real values into destination.""" + ... + @overload - def encode(self, value: float, scale: float) -> Plaintext: ... + def encode(self, value: float, scale: float) -> Plaintext: + """Encode one real value and return the plaintext.""" + ... + @overload - def encode(self, value: float, scale: float, destination: Plaintext) -> None: ... + def encode(self, value: float, scale: float, destination: Plaintext) -> None: + """Encode one real value into destination.""" + ... + @overload - def encode(self, value: int) -> Plaintext: ... + def encode(self, value: int) -> Plaintext: + """Encode one integer exactly into a CKKS plaintext.""" + ... + @overload - def encode(self, value: int, destination: Plaintext) -> None: ... + def encode(self, value: int, destination: Plaintext) -> None: + """Encode one integer exactly into destination.""" + ... + @overload - def encode_complex(self, values: ComplexLikeArray, scale: float) -> Plaintext: ... + def encode_complex(self, values: ComplexLikeArray, scale: float) -> Plaintext: + """Encode complex values and return the plaintext.""" + ... + @overload - def encode_complex(self, values: ComplexLikeArray, scale: float, destination: Plaintext) -> None: ... + def encode_complex(self, values: ComplexLikeArray, scale: float, destination: Plaintext) -> None: + """Encode complex values into destination.""" + ... + @overload - def encode_complex(self, value: complex, scale: float) -> Plaintext: ... + def encode_complex(self, value: complex, scale: float) -> Plaintext: + """Encode one complex value and return the plaintext.""" + ... + @overload - def encode_complex(self, value: complex, scale: float, destination: Plaintext) -> None: ... - def decode(self, plain: Plaintext) -> NDArray[np.float64]: ... - def decode_complex(self, plain: Plaintext) -> NDArray[np.complex128]: ... + def encode_complex(self, value: complex, scale: float, destination: Plaintext) -> None: + """Encode one complex value into destination.""" + ... + + def decode(self, plain: Plaintext) -> NDArray[np.float64]: + """Decode a CKKS plaintext into a NumPy array of real values.""" + ... + + def decode_complex(self, plain: Plaintext) -> NDArray[np.complex128]: + """Decode a CKKS plaintext into a NumPy array of complex values.""" + ... class Decryptor: - def __init__(self, context: SEALContext, secret_key: SecretKey) -> None: ... + """Decrypt ciphertexts and inspect their remaining noise budget.""" + + def __init__(self, context: SEALContext, secret_key: SecretKey) -> None: + """Create a decryptor for the given context and secret key.""" + ... + @overload - def decrypt(self, encrypted: Ciphertext, destination: Plaintext) -> None: ... + def decrypt(self, encrypted: Ciphertext, destination: Plaintext) -> None: + """Decrypt a ciphertext into destination.""" + ... + @overload - def decrypt(self, encrypted: Ciphertext) -> Plaintext: ... - def invariant_noise_budget(self, encrypted: Ciphertext) -> int: ... + def decrypt(self, encrypted: Ciphertext) -> Plaintext: + """Decrypt a ciphertext and return the plaintext.""" + ... + + def invariant_noise_budget(self, encrypted: Ciphertext) -> int: + """Return the invariant noise budget of a ciphertext in bits.""" + ... class BatchEncoder: - def __init__(self, context: SEALContext) -> None: ... - def slot_count(self) -> int: ... + """Encode integer vectors into BFV/BGV batching plaintexts.""" + + def __init__(self, context: SEALContext) -> None: + """Create a batch encoder for the given context.""" + ... + + def slot_count(self) -> int: + """Return the number of batching slots available.""" + ... + @overload - def encode(self, values: Sequence[int], destination: Plaintext) -> None: ... + def encode(self, values: Sequence[int], destination: Plaintext) -> None: + """Encode integers into destination.""" + ... + @overload - def encode(self, values: IntLikeArray) -> Plaintext: ... - def decode(self, plain: Plaintext) -> NDArray[np.int64]: ... - def decode_uint64(self, plain: Plaintext) -> NDArray[np.uint64]: ... + def encode(self, values: IntLikeArray) -> Plaintext: + """Encode integers and return the plaintext.""" + ... + + def decode(self, plain: Plaintext) -> NDArray[np.int64]: + """Decode a batched plaintext into signed 64-bit integers.""" + ... + + def decode_uint64(self, plain: Plaintext) -> NDArray[np.uint64]: + """Decode a batched plaintext into unsigned 64-bit integers.""" + ... diff --git a/setup.py b/setup.py index 0d2862b..07c08bc 100644 --- a/setup.py +++ b/setup.py @@ -4,14 +4,16 @@ from pathlib import Path from shutil import copy2 import sys +import sysconfig from setuptools import setup from distutils.sysconfig import get_python_inc +import pybind11 from pybind11.setup_helpers import Pybind11Extension, build_ext __version__ = "4.1.2" BASE_DIR = Path(__file__).resolve().parent -include_dirs = [get_python_inc(), 'pybind11/include', 'SEAL/native/src', 'SEAL/build/native/src'] +include_dirs = [get_python_inc(), pybind11.get_include(), 'SEAL/native/src', 'SEAL/build/native/src'] if platform.system() == "Windows": extra_objects = sorted( @@ -22,10 +24,21 @@ else: extra_objects = sorted(glob('SEAL/build/lib/*.a')) -cpp_args = ['/std:c++latest'] if platform.system() == "Windows" else ['-std=c++17'] +cpp_args = ['/std:c++17', '/utf-8', '/bigobj'] if platform.system() == "Windows" else ['-std=c++17'] +link_args = [] + +if platform.system() == "Darwin": + deployment_target = os.environ.get("MACOSX_DEPLOYMENT_TARGET") or sysconfig.get_config_var( + "MACOSX_DEPLOYMENT_TARGET" + ) + if deployment_target: + deployment_flag = f"-mmacosx-version-min={deployment_target}" + os.environ.setdefault("MACOSX_DEPLOYMENT_TARGET", deployment_target) + cpp_args.append(deployment_flag) + link_args.append(deployment_flag) if len(extra_objects) < 1 or not os.path.exists(extra_objects[0]): - print('Not found the seal lib file, check the `SEAL/build/lib`') + print('Not found the seal lib file, check the "SEAL/build/lib"') sys.exit(1) @@ -59,8 +72,9 @@ def _copy_typing_files(self): sorted(glob('src/*.cpp')), include_dirs=include_dirs, extra_compile_args=cpp_args, + extra_link_args=link_args, extra_objects=extra_objects, - define_macros = [('VERSION_INFO', __version__)], + define_macros=[('VERSION_INFO', __version__)], ), ] @@ -71,7 +85,7 @@ def _copy_typing_files(self): author_email="topmaxz@protonmail.com", url="https://github.com/Huelse/SEAL-Python", description="Python wrapper for the Microsoft SEAL", - long_description=(BASE_DIR / "README.md").read_text(encoding="utf-8"), + long_description=(BASE_DIR / "README.md").read_text(encoding="utf-8")[:528], long_description_content_type="text/markdown", ext_modules=ext_modules, cmdclass={"build_ext": build_ext_with_typing}, @@ -82,7 +96,6 @@ def _copy_typing_files(self): "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.8", @@ -90,6 +103,8 @@ def _copy_typing_files(self): "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", "Programming Language :: C++", "Topic :: Security :: Cryptography", ], diff --git a/src/wrapper.cpp b/src/wrapper.cpp index 06b298d..5516afa 100644 --- a/src/wrapper.cpp +++ b/src/wrapper.cpp @@ -8,6 +8,8 @@ using namespace seal; namespace py = pybind11; +#define SEAL_DOC(text) text + PYBIND11_MAKE_OPAQUE(std::vector); PYBIND11_MAKE_OPAQUE(std::vector>); PYBIND11_MAKE_OPAQUE(std::vector); @@ -18,20 +20,28 @@ PYBIND11_MODULE(seal, m) m.doc() = "Microsoft SEAL for Python, from https://github.com/Huelse/SEAL-Python"; m.attr("__version__") = "4.1.2"; - py::bind_vector>(m, "VectorDouble", py::buffer_protocol()); - py::bind_vector>>(m, "VectorComplex", py::buffer_protocol()); - py::bind_vector>(m, "VectorUInt", py::buffer_protocol()); - py::bind_vector>(m, "VectorInt", py::buffer_protocol()); + py::bind_vector>( + m, "VectorDouble", py::buffer_protocol(), + SEAL_DOC("Vector container for double values used by SEAL encoders.")); + py::bind_vector>>( + m, "VectorComplex", py::buffer_protocol(), + SEAL_DOC("Vector container for complex values used by CKKS.")); + py::bind_vector>( + m, "VectorUInt", py::buffer_protocol(), + SEAL_DOC("Vector container for unsigned 64-bit integer slots.")); + py::bind_vector>( + m, "VectorInt", py::buffer_protocol(), + SEAL_DOC("Vector container for signed 64-bit integer slots.")); // encryptionparams.h - py::enum_(m, "scheme_type") + py::enum_(m, "scheme_type", SEAL_DOC("Describes the homomorphic encryption scheme to use.")) .value("none", scheme_type::none) .value("bfv", scheme_type::bfv) .value("ckks", scheme_type::ckks) .value("bgv", scheme_type::bgv); // serialization.h - py::enum_(m, "compr_mode_type") + py::enum_(m, "compr_mode_type", SEAL_DOC("Compression mode used when serializing SEAL objects.")) .value("none", compr_mode_type::none) #ifdef SEAL_USE_ZLIB .value("zlib", compr_mode_type::zlib) @@ -42,63 +52,83 @@ PYBIND11_MODULE(seal, m) ; // memorymanager.h - py::class_(m, "MemoryPoolHandle") - .def(py::init<>()) - .def_static("Global", &MemoryPoolHandle::Global) + py::class_( + m, "MemoryPoolHandle", + SEAL_DOC("Handle to a memory pool used by SEAL for efficient temporary allocations.")) + .def(py::init<>(), SEAL_DOC("Construct an uninitialized memory pool handle.")) + .def_static("Global", &MemoryPoolHandle::Global, SEAL_DOC("Return a handle to the global memory pool.")) #ifndef _M_CEE - .def_static("ThreadLocal", &MemoryPoolHandle::ThreadLocal) + .def_static("ThreadLocal", &MemoryPoolHandle::ThreadLocal, SEAL_DOC("Return a handle to the thread-local memory pool.")) #endif - .def_static("New", &MemoryPoolHandle::New, py::arg("clear_on_destruction") = false) - .def("pool_count", &MemoryPoolHandle::pool_count) - .def("alloc_byte_count", &MemoryPoolHandle::alloc_byte_count) - .def("use_count", &MemoryPoolHandle::use_count) + .def_static( + "New", &MemoryPoolHandle::New, py::arg("clear_on_destruction") = false, + SEAL_DOC("Create a new independent memory pool.")) + .def("pool_count", &MemoryPoolHandle::pool_count, SEAL_DOC("Return the number of memory pools referenced by this handle.")) + .def("alloc_byte_count", &MemoryPoolHandle::alloc_byte_count, SEAL_DOC("Return the number of bytes allocated by the pool.")) + .def("use_count", &MemoryPoolHandle::use_count, SEAL_DOC("Return the reference count of the underlying pool.")) .def("is_initialized", [](const MemoryPoolHandle &pool){ return static_cast(pool); - }); + }, SEAL_DOC("Return True if this handle points to an initialized memory pool.")); - py::class_(m, "MemoryManager") + py::class_(m, "MemoryManager", SEAL_DOC("Factory for retrieving SEAL memory pools.")) .def_static("GetPool", [](){ return MemoryManager::GetPool(); - }); + }, SEAL_DOC("Return the default memory pool handle.")); // encryptionparams.h - py::class_(m, "EncryptionParameters") - .def(py::init()) - .def(py::init()) - .def("set_poly_modulus_degree", &EncryptionParameters::set_poly_modulus_degree) - .def("set_coeff_modulus", &EncryptionParameters::set_coeff_modulus) - .def("set_plain_modulus", py::overload_cast(&EncryptionParameters::set_plain_modulus)) - .def("set_plain_modulus", py::overload_cast(&EncryptionParameters::set_plain_modulus)) - .def("scheme", &EncryptionParameters::scheme) - .def("poly_modulus_degree", &EncryptionParameters::poly_modulus_degree) - .def("coeff_modulus", &EncryptionParameters::coeff_modulus) - .def("plain_modulus", &EncryptionParameters::plain_modulus) + py::class_( + m, "EncryptionParameters", + SEAL_DOC("Represents user-configurable encryption settings such as polynomial modulus, coefficient modulus, and plaintext modulus.")) + .def(py::init(), py::arg("scheme"), + SEAL_DOC("Create an empty set of encryption parameters for the given scheme.")) + .def(py::init(), py::arg("copy"), + SEAL_DOC("Create a copy of an existing EncryptionParameters object.")) + .def("set_poly_modulus_degree", &EncryptionParameters::set_poly_modulus_degree, py::arg("poly_modulus_degree"), + SEAL_DOC("Set the degree of the polynomial modulus. In SEAL this must be a power of two.")) + .def("set_coeff_modulus", &EncryptionParameters::set_coeff_modulus, py::arg("coeff_modulus"), + SEAL_DOC("Set the coefficient modulus as a list of distinct prime Modulus values.")) + .def("set_plain_modulus", py::overload_cast(&EncryptionParameters::set_plain_modulus), + py::arg("plain_modulus"), + SEAL_DOC("Set the plaintext modulus using a Modulus object.")) + .def("set_plain_modulus", py::overload_cast(&EncryptionParameters::set_plain_modulus), + py::arg("plain_modulus"), + SEAL_DOC("Set the plaintext modulus from an integer value.")) + .def("scheme", &EncryptionParameters::scheme, SEAL_DOC("Return the selected encryption scheme.")) + .def("poly_modulus_degree", &EncryptionParameters::poly_modulus_degree, SEAL_DOC("Return the degree of the polynomial modulus.")) + .def("coeff_modulus", &EncryptionParameters::coeff_modulus, SEAL_DOC("Return the coefficient modulus chain.")) + .def("plain_modulus", &EncryptionParameters::plain_modulus, SEAL_DOC("Return the plaintext modulus.")) .def("save", [](const EncryptionParameters &parms, std::string &path){ std::ofstream out(path, std::ios::binary); parms.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the encryption parameters to a file.")) .def("save", [](const EncryptionParameters &parms, std::string &path, compr_mode_type compr_mode){ std::ofstream out(path, std::ios::binary); parms.save(out, compr_mode); out.close(); - }) + }, py::arg("path"), py::arg("compr_mode"), + SEAL_DOC("Serialize the encryption parameters to a file using the given compression mode.")) .def("load", [](EncryptionParameters &parms, std::string &path){ std::ifstream in(path, std::ios::binary); parms.load(in); in.close(); - }) + }, py::arg("path"), + SEAL_DOC("Load serialized encryption parameters from a file.")) .def("load_bytes", [](EncryptionParameters &parms, py::bytes data){ std::string raw = data; parms.load(reinterpret_cast(raw.data()), raw.size()); - }) + }, py::arg("data"), + SEAL_DOC("Load serialized encryption parameters from a bytes object.")) .def("save_size", py::overload_cast(&EncryptionParameters::save_size, py::const_), - py::arg("compr_mode")=Serialization::compr_mode_default) + py::arg("compr_mode")=Serialization::compr_mode_default, + SEAL_DOC("Return the serialized size in bytes for the given compression mode.")) .def("to_bytes", [](const EncryptionParameters &parms, compr_mode_type compr_mode){ std::stringstream out(std::ios::binary | std::ios::out); parms.save(out, compr_mode); return py::bytes(out.str()); - }, py::arg("compr_mode")=Serialization::compr_mode_default) + }, py::arg("compr_mode")=Serialization::compr_mode_default, + SEAL_DOC("Serialize the encryption parameters to a Python bytes object.")) .def(py::pickle( [](const EncryptionParameters &parms){ std::stringstream out(std::ios::binary | std::ios::out); @@ -118,14 +148,16 @@ PYBIND11_MODULE(seal, m) )); // modulus.h - py::enum_(m, "sec_level_type") + py::enum_(m, "sec_level_type", SEAL_DOC("HomomorphicEncryption.org standard security level.")) .value("none", sec_level_type::none) .value("tc128", sec_level_type::tc128) .value("tc192", sec_level_type::tc192) .value("tc256", sec_level_type::tc256); // context.h - py::enum_(m, "error_type") + py::enum_( + m, "error_type", + SEAL_DOC("Reason why a set of encryption parameters is invalid.")) .value("none", EncryptionParameterQualifiers::error_type::none) .value("success", EncryptionParameterQualifiers::error_type::success) .value("invalid_scheme", EncryptionParameterQualifiers::error_type::invalid_scheme) @@ -144,576 +176,754 @@ PYBIND11_MODULE(seal, m) .value("failed_creating_rns_tool", EncryptionParameterQualifiers::error_type::failed_creating_rns_tool); // context.h - py::class_>(m, "EncryptionParameterQualifiers") - .def("parameters_set", &EncryptionParameterQualifiers::parameters_set) - .def_readwrite("parameter_error", &EncryptionParameterQualifiers::parameter_error) - .def("parameter_error_name", &EncryptionParameterQualifiers::parameter_error_name) - .def("parameter_error_message", &EncryptionParameterQualifiers::parameter_error_message) - .def_readwrite("using_fft", &EncryptionParameterQualifiers::using_fft) - .def_readwrite("using_ntt", &EncryptionParameterQualifiers::using_ntt) - .def_readwrite("using_batching", &EncryptionParameterQualifiers::using_batching) - .def_readwrite("using_fast_plain_lift", &EncryptionParameterQualifiers::using_fast_plain_lift) - .def_readwrite("using_descending_modulus_chain", &EncryptionParameterQualifiers::using_descending_modulus_chain) - .def_readwrite("sec_level", &EncryptionParameterQualifiers::sec_level); + py::class_>( + m, "EncryptionParameterQualifiers", + SEAL_DOC("Stores pre-computed attributes and validation results for encryption parameters.")) + .def("parameters_set", &EncryptionParameterQualifiers::parameters_set, + SEAL_DOC("Return True if the parameters were validated successfully.")) + .def_readwrite("parameter_error", &EncryptionParameterQualifiers::parameter_error, + "Validation error code for the parameter set.") + .def("parameter_error_name", &EncryptionParameterQualifiers::parameter_error_name, + SEAL_DOC("Return the symbolic name of the validation error.")) + .def("parameter_error_message", &EncryptionParameterQualifiers::parameter_error_message, + SEAL_DOC("Return a human-readable explanation of the validation error.")) + .def_readwrite("using_fft", &EncryptionParameterQualifiers::using_fft, + "Whether FFT can be used for polynomial multiplication.") + .def_readwrite("using_ntt", &EncryptionParameterQualifiers::using_ntt, + "Whether NTT can be used for polynomial multiplication.") + .def_readwrite("using_batching", &EncryptionParameterQualifiers::using_batching, + "Whether SIMD batching is supported.") + .def_readwrite("using_fast_plain_lift", &EncryptionParameterQualifiers::using_fast_plain_lift, + "Whether fast plain lift is available.") + .def_readwrite("using_descending_modulus_chain", &EncryptionParameterQualifiers::using_descending_modulus_chain, + "Whether the coefficient modulus primes are in descending order.") + .def_readwrite("sec_level", &EncryptionParameterQualifiers::sec_level, + "Security level classification for the parameters."); // context.h - py::class_>(m, "ContextData") - .def("parms", &SEALContext::ContextData::parms) - .def("parms_id", &SEALContext::ContextData::parms_id) - .def("qualifiers", &SEALContext::ContextData::qualifiers) - .def("total_coeff_modulus", &SEALContext::ContextData::total_coeff_modulus) - .def("total_coeff_modulus_bit_count", &SEALContext::ContextData::total_coeff_modulus_bit_count) - .def("next_context_data", &SEALContext::ContextData::next_context_data) - .def("chain_index", &SEALContext::ContextData::chain_index); + py::class_>( + m, "ContextData", + SEAL_DOC("Pre-computation data associated with one specific parameter set in the modulus switching chain.")) + .def("parms", &SEALContext::ContextData::parms, SEAL_DOC("Return the encryption parameters for this context level.")) + .def("parms_id", &SEALContext::ContextData::parms_id, SEAL_DOC("Return the unique parameter identifier for this context level.")) + .def("qualifiers", &SEALContext::ContextData::qualifiers, SEAL_DOC("Return qualifiers derived from these parameters.")) + .def("total_coeff_modulus", &SEALContext::ContextData::total_coeff_modulus, SEAL_DOC("Return the product of all coefficient modulus primes.")) + .def("total_coeff_modulus_bit_count", &SEALContext::ContextData::total_coeff_modulus_bit_count, SEAL_DOC("Return the bit count of the total coefficient modulus.")) + .def("next_context_data", &SEALContext::ContextData::next_context_data, SEAL_DOC("Return the next lower level in the modulus switching chain.")) + .def("chain_index", &SEALContext::ContextData::chain_index, SEAL_DOC("Return the chain index for this context level.")); // context.h - py::class_>(m, "SEALContext") - .def(py::init(), py::arg(), py::arg()=true, py::arg()=sec_level_type::tc128) - .def("get_context_data", &SEALContext::get_context_data) - .def("key_context_data", &SEALContext::key_context_data) - .def("first_context_data", &SEALContext::first_context_data) - .def("last_context_data", &SEALContext::last_context_data) - .def("parameters_set", &SEALContext::parameters_set) - .def("parameter_error_name", &SEALContext::parameter_error_name) - .def("parameter_error_message", &SEALContext::parameter_error_message) - .def("first_parms_id", &SEALContext::first_parms_id) - .def("last_parms_id", &SEALContext::last_parms_id) - .def("using_keyswitching", &SEALContext::using_keyswitching) + py::class_>( + m, "SEALContext", + SEAL_DOC("Validates encryption parameters and stores heavy-weight pre-computations used by SEAL operations.")) + .def(py::init(), + py::arg("parms"), py::arg("expand_mod_chain")=true, py::arg("sec_level")=sec_level_type::tc128, + SEAL_DOC("Create a SEALContext from encryption parameters and optionally expand the modulus switching chain.")) + .def("get_context_data", &SEALContext::get_context_data, py::arg("parms_id"), + SEAL_DOC("Return the ContextData for a specific parms_id.")) + .def("key_context_data", &SEALContext::key_context_data, SEAL_DOC("Return the key-level ContextData.")) + .def("first_context_data", &SEALContext::first_context_data, SEAL_DOC("Return the first data-level ContextData in the chain.")) + .def("last_context_data", &SEALContext::last_context_data, SEAL_DOC("Return the last valid ContextData in the chain.")) + .def("parameters_set", &SEALContext::parameters_set, SEAL_DOC("Return True if the parameters were validated successfully.")) + .def("parameter_error_name", &SEALContext::parameter_error_name, SEAL_DOC("Return the symbolic name of the validation result.")) + .def("parameter_error_message", &SEALContext::parameter_error_message, SEAL_DOC("Return a human-readable validation message.")) + .def("first_parms_id", &SEALContext::first_parms_id, SEAL_DOC("Return the parms_id for the first data-level parameters.")) + .def("last_parms_id", &SEALContext::last_parms_id, SEAL_DOC("Return the parms_id for the last valid parameters in the chain.")) + .def("using_keyswitching", &SEALContext::using_keyswitching, SEAL_DOC("Return True if the parameter chain supports key switching.")) .def("from_cipher_str", [](const SEALContext &context, const std::string &str){ Ciphertext cipher; std::stringstream in(std::ios::binary | std::ios::in); in.str(str); cipher.load(context, in); return cipher; - }) + }, py::arg("data"), + SEAL_DOC("Deserialize a Ciphertext from a serialized bytes-like string.")) .def("from_plain_str", [](const SEALContext &context, const std::string &str){ Plaintext plain; std::stringstream in(std::ios::binary | std::ios::in); in.str(str); plain.load(context, in); return plain; - }) + }, py::arg("data"), + SEAL_DOC("Deserialize a Plaintext from a serialized bytes-like string.")) .def("from_secret_str", [](const SEALContext &context, const std::string &str){ SecretKey secret; std::stringstream in(std::ios::binary | std::ios::in); in.str(str); secret.load(context, in); return secret; - }) + }, py::arg("data"), + SEAL_DOC("Deserialize a SecretKey from a serialized bytes-like string.")) .def("from_public_str", [](const SEALContext &context, const std::string &str){ PublicKey public_; std::stringstream in(std::ios::binary | std::ios::in); in.str(str); public_.load(context, in); return public_; - }) + }, py::arg("data"), + SEAL_DOC("Deserialize a PublicKey from a serialized bytes-like string.")) .def("from_relin_str", [](const SEALContext &context, const std::string &str){ RelinKeys relin; std::stringstream in(std::ios::binary | std::ios::in); in.str(str); relin.load(context, in); return relin; - }) + }, py::arg("data"), + SEAL_DOC("Deserialize RelinKeys from a serialized bytes-like string.")) .def("from_galois_str", [](const SEALContext &context, const std::string &str){ GaloisKeys galois; std::stringstream in(std::ios::binary | std::ios::in); in.str(str); galois.load(context, in); return galois; - }); + }, py::arg("data"), + SEAL_DOC("Deserialize GaloisKeys from a serialized bytes-like string.")); // modulus.h - py::class_(m, "Modulus") - .def(py::init()) - .def("bit_count", &Modulus::bit_count) - .def("value", &Modulus::value) - .def("is_zero", &Modulus::is_zero) - .def("is_prime", &Modulus::is_prime) - .def("reduce", &Modulus::reduce); + py::class_(m, "Modulus", SEAL_DOC("Represents an integer modulus used in encryption parameters.")) + .def(py::init(), py::arg("value"), + SEAL_DOC("Construct a modulus from an unsigned 64-bit integer.")) + .def("bit_count", &Modulus::bit_count, SEAL_DOC("Return the bit length of the modulus.")) + .def("value", &Modulus::value, SEAL_DOC("Return the numeric value of the modulus.")) + .def("is_zero", &Modulus::is_zero, SEAL_DOC("Return True if the modulus is zero.")) + .def("is_prime", &Modulus::is_prime, SEAL_DOC("Return True if the modulus value is prime.")) + .def("reduce", &Modulus::reduce, py::arg("value"), + SEAL_DOC("Reduce an integer modulo this modulus.")); //save & load // modulus.h - py::class_(m, "CoeffModulus") - .def_static("MaxBitCount", &CoeffModulus::MaxBitCount, py::arg(), py::arg()=sec_level_type::tc128) - .def_static("BFVDefault", &CoeffModulus::BFVDefault, py::arg(), py::arg()=sec_level_type::tc128) - .def_static("Create", py::overload_cast>(&CoeffModulus::Create)) - .def_static("Create", py::overload_cast>(&CoeffModulus::Create)); + py::class_(m, "CoeffModulus", SEAL_DOC("Factory helpers for constructing coefficient modulus chains.")) + .def_static("MaxBitCount", &CoeffModulus::MaxBitCount, py::arg("poly_modulus_degree"), py::arg("sec_level")=sec_level_type::tc128, + SEAL_DOC("Return the maximum safe total bit count for the coefficient modulus.")) + .def_static("BFVDefault", &CoeffModulus::BFVDefault, py::arg("poly_modulus_degree"), py::arg("sec_level")=sec_level_type::tc128, + SEAL_DOC("Return SEAL's default BFV/BGV coefficient modulus for the requested security level.")) + .def_static("Create", py::overload_cast>(&CoeffModulus::Create), + py::arg("poly_modulus_degree"), py::arg("bit_sizes"), + SEAL_DOC("Create a custom coefficient modulus chain with primes of the given bit sizes.")) + .def_static("Create", py::overload_cast>(&CoeffModulus::Create), + py::arg("poly_modulus_degree"), py::arg("plain_modulus"), py::arg("bit_sizes"), + SEAL_DOC("Create a custom coefficient modulus chain tailored to batching/plain modulus constraints.")); // modulus.h - py::class_(m, "PlainModulus") - .def_static("Batching", py::overload_cast(&PlainModulus::Batching)) - .def_static("Batching", py::overload_cast>(&PlainModulus::Batching)); + py::class_(m, "PlainModulus", SEAL_DOC("Factory helpers for constructing plaintext moduli.")) + .def_static("Batching", py::overload_cast(&PlainModulus::Batching), + py::arg("poly_modulus_degree"), py::arg("bit_size"), + SEAL_DOC("Create one batching-compatible plaintext modulus with the given bit size.")) + .def_static("Batching", py::overload_cast>(&PlainModulus::Batching), + py::arg("poly_modulus_degree"), py::arg("bit_sizes"), + SEAL_DOC("Create batching-compatible plaintext moduli for the requested bit sizes.")); // plaintext.h - py::class_(m, "Plaintext") - .def(py::init<>()) - .def(py::init<std::size_t>()) - .def(py::init<std::size_t, std::size_t>()) - .def(py::init<const std::string &>()) - .def(py::init<const Plaintext &>()) - .def("set_zero", py::overload_cast<std::size_t, std::size_t>(&Plaintext::set_zero)) - .def("set_zero", py::overload_cast<std::size_t>(&Plaintext::set_zero)) - .def("set_zero", py::overload_cast<>(&Plaintext::set_zero)) - .def("is_zero", &Plaintext::is_zero) - .def("capacity", &Plaintext::capacity) - .def("coeff_count", &Plaintext::coeff_count) - .def("significant_coeff_count", &Plaintext::significant_coeff_count) - .def("nonzero_coeff_count", &Plaintext::nonzero_coeff_count) - .def("to_string", &Plaintext::to_string) - .def("is_ntt_form", &Plaintext::is_ntt_form) - .def("parms_id", py::overload_cast<>(&Plaintext::parms_id, py::const_)) - .def("scale", py::overload_cast<>(&Plaintext::scale, py::const_)) + py::class_<Plaintext>( + m, "Plaintext", + SEAL_DOC("Stores a plaintext polynomial. In CKKS, plaintexts are typically kept in NTT form and also carry a scale.")) + .def(py::init<>(), SEAL_DOC("Construct an empty plaintext with no allocated data.")) + .def(py::init<std::size_t>(), py::arg("coeff_count"), + SEAL_DOC("Construct a zero plaintext with the given coefficient count.")) + .def(py::init<std::size_t, std::size_t>(), py::arg("capacity"), py::arg("coeff_count"), + SEAL_DOC("Construct a zero plaintext with explicit capacity and coefficient count.")) + .def(py::init<const std::string &>(), py::arg("hex_poly"), + SEAL_DOC("Construct a plaintext from the hexadecimal polynomial format returned by to_string().")) + .def(py::init<const Plaintext &>(), py::arg("copy"), + SEAL_DOC("Construct a copy of an existing plaintext.")) + .def("set_zero", py::overload_cast<std::size_t, std::size_t>(&Plaintext::set_zero), + py::arg("start_coeff"), py::arg("length"), + SEAL_DOC("Set a range of coefficients to zero.")) + .def("set_zero", py::overload_cast<std::size_t>(&Plaintext::set_zero), py::arg("start_coeff"), + SEAL_DOC("Set coefficients from start_coeff to the end to zero.")) + .def("set_zero", py::overload_cast<>(&Plaintext::set_zero), + SEAL_DOC("Set all coefficients to zero.")) + .def("is_zero", &Plaintext::is_zero, SEAL_DOC("Return True if all coefficients are zero.")) + .def("capacity", &Plaintext::capacity, SEAL_DOC("Return the allocation capacity measured in coefficients.")) + .def("coeff_count", &Plaintext::coeff_count, SEAL_DOC("Return the number of coefficients stored in the plaintext.")) + .def("significant_coeff_count", &Plaintext::significant_coeff_count, SEAL_DOC("Return the number of significant coefficients.")) + .def("nonzero_coeff_count", &Plaintext::nonzero_coeff_count, SEAL_DOC("Return the number of non-zero coefficients.")) + .def("to_string", &Plaintext::to_string, SEAL_DOC("Return the plaintext polynomial formatted as a hexadecimal string.")) + .def("is_ntt_form", &Plaintext::is_ntt_form, SEAL_DOC("Return True if the plaintext is stored in NTT form.")) + .def("parms_id", py::overload_cast<>(&Plaintext::parms_id, py::const_), + SEAL_DOC("Return the parms_id associated with this plaintext.")) + .def("scale", py::overload_cast<>(&Plaintext::scale, py::const_), + SEAL_DOC("Return the CKKS scale attached to this plaintext.")) .def("scale", [](Plaintext &plain, double scale){ plain.scale() = scale; - }) + }, py::arg("scale"), + SEAL_DOC("Set the CKKS scale attached to this plaintext.")) .def("save", [](const Plaintext &plain, const std::string &path){ std::ofstream out(path, std::ios::binary); plain.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the plaintext to a file.")) .def("save", [](const Plaintext &plain, const std::string &path, compr_mode_type compr_mode){ std::ofstream out(path, std::ios::binary); plain.save(out, compr_mode); out.close(); - }) + }, py::arg("path"), py::arg("compr_mode"), + SEAL_DOC("Serialize the plaintext to a file using the given compression mode.")) .def("load", [](Plaintext &plain, const SEALContext &context, const std::string &path){ std::ifstream in(path, std::ios::binary); plain.load(context, in); in.close(); - }) + }, py::arg("context"), py::arg("path"), + SEAL_DOC("Load a serialized plaintext from a file and validate it against the context.")) .def("load_bytes", [](Plaintext &plain, const SEALContext &context, py::bytes data){ std::string raw = data; plain.load(context, reinterpret_cast<const seal_byte *>(raw.data()), raw.size()); - }) + }, py::arg("context"), py::arg("data"), + SEAL_DOC("Load a serialized plaintext from a bytes object and validate it against the context.")) .def("save_size", [](const Plaintext &plain){ return plain.save_size(); - }) + }, SEAL_DOC("Return the serialized size in bytes using the default compression mode.")) .def("save_size", py::overload_cast<compr_mode_type>(&Plaintext::save_size, py::const_), - py::arg("compr_mode")=Serialization::compr_mode_default) + py::arg("compr_mode")=Serialization::compr_mode_default, + SEAL_DOC("Return the serialized size in bytes for the given compression mode.")) .def("to_bytes", [](const Plaintext &plain, compr_mode_type compr_mode){ std::stringstream out(std::ios::binary | std::ios::out); plain.save(out, compr_mode); return py::bytes(out.str()); - }, py::arg("compr_mode")=Serialization::compr_mode_default); + }, py::arg("compr_mode")=Serialization::compr_mode_default, + SEAL_DOC("Serialize the plaintext to a Python bytes object.")); // ciphertext.h - py::class_<Ciphertext>(m, "Ciphertext") - .def(py::init<>()) - .def(py::init<const SEALContext &>()) - .def(py::init<const SEALContext &, parms_id_type>()) - .def(py::init<const SEALContext &, parms_id_type, std::size_t>()) - .def(py::init<const Ciphertext &>()) - .def("coeff_modulus_size", &Ciphertext::coeff_modulus_size) - .def("poly_modulus_degree", &Ciphertext::poly_modulus_degree) - .def("size", &Ciphertext::size) - .def("size_capacity", &Ciphertext::size_capacity) - .def("is_transparent", &Ciphertext::is_transparent) - .def("is_ntt_form", py::overload_cast<>(&Ciphertext::is_ntt_form, py::const_)) - .def("parms_id", py::overload_cast<>(&Ciphertext::parms_id, py::const_)) - .def("scale", py::overload_cast<>(&Ciphertext::scale, py::const_)) + py::class_<Ciphertext>( + m, "Ciphertext", + SEAL_DOC("Stores an encrypted value as two or more CRT polynomials together with parameter metadata.")) + .def(py::init<>(), SEAL_DOC("Construct an empty ciphertext with no allocated data.")) + .def(py::init<const SEALContext &>(), py::arg("context"), + SEAL_DOC("Construct an empty ciphertext initialized for the highest data level in the context.")) + .def(py::init<const SEALContext &, parms_id_type>(), py::arg("context"), py::arg("parms_id"), + SEAL_DOC("Construct an empty ciphertext initialized for a specific parms_id.")) + .def(py::init<const SEALContext &, parms_id_type, std::size_t>(), + py::arg("context"), py::arg("parms_id"), py::arg("size_capacity"), + SEAL_DOC("Construct an empty ciphertext with explicit polynomial capacity.")) + .def(py::init<const Ciphertext &>(), py::arg("copy"), + SEAL_DOC("Construct a copy of an existing ciphertext.")) + .def("coeff_modulus_size", &Ciphertext::coeff_modulus_size, SEAL_DOC("Return the number of coefficient modulus primes.")) + .def("poly_modulus_degree", &Ciphertext::poly_modulus_degree, SEAL_DOC("Return the polynomial modulus degree.")) + .def("size", &Ciphertext::size, SEAL_DOC("Return the number of polynomials in the ciphertext.")) + .def("size_capacity", &Ciphertext::size_capacity, SEAL_DOC("Return the allocated ciphertext capacity measured in polynomials.")) + .def("is_transparent", &Ciphertext::is_transparent, SEAL_DOC("Return True if the ciphertext is transparent, which is generally insecure.")) + .def("is_ntt_form", py::overload_cast<>(&Ciphertext::is_ntt_form, py::const_), + SEAL_DOC("Return True if the ciphertext is stored in NTT form.")) + .def("parms_id", py::overload_cast<>(&Ciphertext::parms_id, py::const_), + SEAL_DOC("Return the parms_id associated with this ciphertext.")) + .def("scale", py::overload_cast<>(&Ciphertext::scale, py::const_), + SEAL_DOC("Return the CKKS scale attached to this ciphertext.")) .def("scale", [](Ciphertext &cipher, double scale){ cipher.scale() = scale; - }) + }, py::arg("scale"), + SEAL_DOC("Set the CKKS scale attached to this ciphertext.")) .def("save", [](const Ciphertext &cipher, const std::string &path){ std::ofstream out(path, std::ios::binary); cipher.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the ciphertext to a file.")) .def("save", [](const Ciphertext &cipher, const std::string &path, compr_mode_type compr_mode){ std::ofstream out(path, std::ios::binary); cipher.save(out, compr_mode); out.close(); - }) + }, py::arg("path"), py::arg("compr_mode"), + SEAL_DOC("Serialize the ciphertext to a file using the given compression mode.")) .def("load", [](Ciphertext &cipher, const SEALContext &context, const std::string &path){ std::ifstream in(path, std::ios::binary); cipher.load(context, in); in.close(); - }) + }, py::arg("context"), py::arg("path"), + SEAL_DOC("Load a serialized ciphertext from a file and validate it against the context.")) .def("load_bytes", [](Ciphertext &cipher, const SEALContext &context, py::bytes data){ std::string raw = data; cipher.load(context, reinterpret_cast<const seal_byte *>(raw.data()), raw.size()); - }) + }, py::arg("context"), py::arg("data"), + SEAL_DOC("Load a serialized ciphertext from a bytes object and validate it against the context.")) .def("save_size", [](const Ciphertext &cipher){ return cipher.save_size(); - }) + }, SEAL_DOC("Return the serialized size in bytes using the default compression mode.")) .def("save_size", py::overload_cast<compr_mode_type>(&Ciphertext::save_size, py::const_), - py::arg("compr_mode")=Serialization::compr_mode_default) + py::arg("compr_mode")=Serialization::compr_mode_default, + SEAL_DOC("Return the serialized size in bytes for the given compression mode.")) .def("to_string", [](const Ciphertext &cipher, compr_mode_type compr_mode){ std::stringstream out(std::ios::binary | std::ios::out); cipher.save(out, compr_mode); return py::bytes(out.str()); - }, py::arg("compr_mode")=Serialization::compr_mode_default); + }, py::arg("compr_mode")=Serialization::compr_mode_default, + SEAL_DOC("Serialize the ciphertext to a Python bytes object.")); // secretkey.h - py::class_<SecretKey>(m, "SecretKey") - .def(py::init<>()) - .def(py::init<const SecretKey &>()) - .def("parms_id", py::overload_cast<>(&SecretKey::parms_id, py::const_)) + py::class_<SecretKey>(m, "SecretKey", SEAL_DOC("Stores the secret key used for decryption and symmetric encryption.")) + .def(py::init<>(), SEAL_DOC("Construct an empty secret key.")) + .def(py::init<const SecretKey &>(), py::arg("copy"), + SEAL_DOC("Construct a copy of an existing secret key.")) + .def("parms_id", py::overload_cast<>(&SecretKey::parms_id, py::const_), + SEAL_DOC("Return the parms_id associated with the secret key.")) .def("save", [](const SecretKey &sk, const std::string &path){ std::ofstream out(path, std::ios::binary); sk.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the secret key to a file.")) .def("load", [](SecretKey &sk, const SEALContext &context, const std::string &path){ std::ifstream in(path, std::ios::binary); sk.load(context, in); in.close(); - }) + }, py::arg("context"), py::arg("path"), + SEAL_DOC("Load a serialized secret key from a file.")) .def("to_string", [](const SecretKey &secret){ std::stringstream out(std::ios::binary | std::ios::out); secret.save(out); return py::bytes(out.str()); - }); + }, SEAL_DOC("Serialize the secret key to a Python bytes object.")); // publickey.h - py::class_<PublicKey>(m, "PublicKey") - .def(py::init<>()) - .def(py::init<const PublicKey &>()) - .def("parms_id", py::overload_cast<>(&PublicKey::parms_id, py::const_)) + py::class_<PublicKey>(m, "PublicKey", SEAL_DOC("Stores the public key used for public-key encryption.")) + .def(py::init<>(), SEAL_DOC("Construct an empty public key.")) + .def(py::init<const PublicKey &>(), py::arg("copy"), + SEAL_DOC("Construct a copy of an existing public key.")) + .def("parms_id", py::overload_cast<>(&PublicKey::parms_id, py::const_), + SEAL_DOC("Return the parms_id associated with the public key.")) .def("save", [](const PublicKey &pk, const std::string &path){ std::ofstream out(path, std::ios::binary); pk.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the public key to a file.")) .def("load", [](PublicKey &pk, const SEALContext &context, const std::string &path){ std::ifstream in(path, std::ios::binary); pk.load(context, in); in.close(); - }) + }, py::arg("context"), py::arg("path"), + SEAL_DOC("Load a serialized public key from a file.")) .def("to_string", [](const PublicKey &public_){ std::stringstream out(std::ios::binary | std::ios::out); public_.save(out); return py::bytes(out.str()); - }); + }, SEAL_DOC("Serialize the public key to a Python bytes object.")); // kswitchkeys.h - py::class_<KSwitchKeys>(m, "KSwitchKeys") - .def(py::init<>()) - .def(py::init<const KSwitchKeys &>()) - .def("size", &KSwitchKeys::size) - .def("parms_id", py::overload_cast<>(&KSwitchKeys::parms_id, py::const_)) + py::class_<KSwitchKeys>(m, "KSwitchKeys", SEAL_DOC("Base container for key switching key material.")) + .def(py::init<>(), SEAL_DOC("Construct an empty key switching key container.")) + .def(py::init<const KSwitchKeys &>(), py::arg("copy"), + SEAL_DOC("Construct a copy of an existing key switching key container.")) + .def("size", &KSwitchKeys::size, SEAL_DOC("Return the number of stored key switching key sets.")) + .def("parms_id", py::overload_cast<>(&KSwitchKeys::parms_id, py::const_), + SEAL_DOC("Return the parms_id associated with this key set.")) .def("save", [](const KSwitchKeys &ksk, const std::string &path){ std::ofstream out(path, std::ios::binary); ksk.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the key switching keys to a file.")) .def("load", [](KSwitchKeys &ksk, const SEALContext &context, const std::string &path){ std::ifstream in(path, std::ios::binary); ksk.load(context, in); in.close(); - }); + }, py::arg("context"), py::arg("path"), + SEAL_DOC("Load serialized key switching keys from a file.")); // relinkeys.h - py::class_<RelinKeys, KSwitchKeys>(m, "RelinKeys") - .def(py::init<>()) - .def(py::init<const RelinKeys::KSwitchKeys &>()) - .def("size", &RelinKeys::KSwitchKeys::size) - .def("parms_id", py::overload_cast<>(&RelinKeys::KSwitchKeys::parms_id, py::const_)) - .def_static("get_index", &RelinKeys::get_index) - .def("has_key", &RelinKeys::has_key) + py::class_<RelinKeys, KSwitchKeys>(m, "RelinKeys", SEAL_DOC("Relinearization keys used to shrink ciphertext size after multiplication.")) + .def(py::init<>(), SEAL_DOC("Construct an empty set of relinearization keys.")) + .def(py::init<const RelinKeys::KSwitchKeys &>(), py::arg("copy"), + SEAL_DOC("Construct relinearization keys from a key switching key base object.")) + .def("size", &RelinKeys::KSwitchKeys::size, SEAL_DOC("Return the number of stored relinearization key sets.")) + .def("parms_id", py::overload_cast<>(&RelinKeys::KSwitchKeys::parms_id, py::const_), + SEAL_DOC("Return the parms_id associated with these relinearization keys.")) + .def_static("get_index", &RelinKeys::get_index, py::arg("key_power"), + SEAL_DOC("Map a key power to the internal storage index used by SEAL.")) + .def("has_key", &RelinKeys::has_key, py::arg("key_power"), + SEAL_DOC("Return True if a relinearization key exists for the given key power.")) .def("save", [](const RelinKeys &rk, const std::string &path){ std::ofstream out(path, std::ios::binary); rk.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the relinearization keys to a file.")) .def("load", [](RelinKeys &rk, const SEALContext &context, const std::string &path){ std::ifstream in(path, std::ios::binary); rk.load(context, in); in.close(); - }) + }, py::arg("context"), py::arg("path"), + SEAL_DOC("Load serialized relinearization keys from a file.")) .def("to_string", [](const RelinKeys &relin){ std::stringstream out(std::ios::binary | std::ios::out); relin.save(out); return py::bytes(out.str()); - }); + }, SEAL_DOC("Serialize the relinearization keys to a Python bytes object.")); // galoiskeys.h - py::class_<GaloisKeys, KSwitchKeys>(m, "GaloisKeys") - .def(py::init<>()) - .def(py::init<const GaloisKeys::KSwitchKeys &>()) - .def("size", &GaloisKeys::KSwitchKeys::size) - .def("parms_id", py::overload_cast<>(&GaloisKeys::KSwitchKeys::parms_id, py::const_)) - .def_static("get_index", &GaloisKeys::get_index) - .def("has_key", &GaloisKeys::has_key) + py::class_<GaloisKeys, KSwitchKeys>(m, "GaloisKeys", SEAL_DOC("Galois keys used for rotations and CKKS complex conjugation.")) + .def(py::init<>(), SEAL_DOC("Construct an empty set of Galois keys.")) + .def(py::init<const GaloisKeys::KSwitchKeys &>(), py::arg("copy"), + SEAL_DOC("Construct Galois keys from a key switching key base object.")) + .def("size", &GaloisKeys::KSwitchKeys::size, SEAL_DOC("Return the number of stored Galois key sets.")) + .def("parms_id", py::overload_cast<>(&GaloisKeys::KSwitchKeys::parms_id, py::const_), + SEAL_DOC("Return the parms_id associated with these Galois keys.")) + .def_static("get_index", &GaloisKeys::get_index, py::arg("galois_elt"), + SEAL_DOC("Map a Galois element to the internal storage index used by SEAL.")) + .def("has_key", &GaloisKeys::has_key, py::arg("galois_elt"), + SEAL_DOC("Return True if a Galois key exists for the given Galois element.")) .def("save", [](const GaloisKeys &gk, const std::string &path){ std::ofstream out(path, std::ios::binary); gk.save(out); out.close(); - }) + }, py::arg("path"), + SEAL_DOC("Serialize the Galois keys to a file.")) .def("load", [](GaloisKeys &gk, const SEALContext &context, const std::string &path){ std::ifstream in(path, std::ios::binary); gk.load(context, in); in.close(); - }) + }, py::arg("context"), py::arg("path"), + SEAL_DOC("Load serialized Galois keys from a file.")) .def("to_string", [](const GaloisKeys &galois){ std::stringstream out(std::ios::binary | std::ios::out); galois.save(out); return py::bytes(out.str()); - }); + }, SEAL_DOC("Serialize the Galois keys to a Python bytes object.")); // keygenerator.h - py::class_<KeyGenerator>(m, "KeyGenerator") - .def(py::init<const SEALContext &>()) - .def(py::init<const SEALContext &, const SecretKey &>()) - .def("secret_key", &KeyGenerator::secret_key) - .def("create_public_key", py::overload_cast<PublicKey &>(&KeyGenerator::create_public_key, py::const_)) - .def("create_relin_keys", py::overload_cast<RelinKeys &>(&KeyGenerator::create_relin_keys)) - .def("create_galois_keys", py::overload_cast<const std::vector<int> &, GaloisKeys &>(&KeyGenerator::create_galois_keys)) - .def("create_galois_keys", py::overload_cast<GaloisKeys &>(&KeyGenerator::create_galois_keys)) + py::class_<KeyGenerator>(m, "KeyGenerator", SEAL_DOC("Generates secret, public, relinearization, and Galois keys for a SEALContext.")) + .def(py::init<const SEALContext &>(), py::arg("context"), + SEAL_DOC("Create a key generator and generate a fresh secret key.")) + .def(py::init<const SEALContext &, const SecretKey &>(), py::arg("context"), py::arg("secret_key"), + SEAL_DOC("Create a key generator from an existing secret key.")) + .def("secret_key", &KeyGenerator::secret_key, SEAL_DOC("Return the secret key managed by this generator.")) + .def("create_public_key", py::overload_cast<PublicKey &>(&KeyGenerator::create_public_key, py::const_), py::arg("destination"), + SEAL_DOC("Generate a public key and store it in destination.")) + .def("create_relin_keys", py::overload_cast<RelinKeys &>(&KeyGenerator::create_relin_keys), py::arg("destination"), + SEAL_DOC("Generate relinearization keys and store them in destination.")) + .def("create_galois_keys", py::overload_cast<const std::vector<int> &, GaloisKeys &>(&KeyGenerator::create_galois_keys), + py::arg("steps"), py::arg("destination"), + SEAL_DOC("Generate Galois keys for the requested rotation steps and store them in destination.")) + .def("create_galois_keys", py::overload_cast<GaloisKeys &>(&KeyGenerator::create_galois_keys), py::arg("destination"), + SEAL_DOC("Generate all supported Galois keys and store them in destination.")) .def("create_public_key", [](KeyGenerator &keygen){ PublicKey pk; keygen.create_public_key(pk); return pk; - }) + }, SEAL_DOC("Generate and return a new public key.")) .def("create_relin_keys", [](KeyGenerator &keygen){ RelinKeys rk; keygen.create_relin_keys(rk); return rk; - }) + }, SEAL_DOC("Generate and return relinearization keys.")) .def("create_galois_keys", [](KeyGenerator &keygen){ GaloisKeys gk; keygen.create_galois_keys(gk); return gk; - }); + }, SEAL_DOC("Generate and return all supported Galois keys.")); // encryptor.h - py::class_<Encryptor>(m, "Encryptor") - .def(py::init<const SEALContext &, const PublicKey &>()) - .def(py::init<const SEALContext &, const SecretKey &>()) - .def(py::init<const SEALContext &, const PublicKey &, const SecretKey &>()) - .def("set_public_key", &Encryptor::set_public_key) - .def("set_secret_key", &Encryptor::set_secret_key) + py::class_<Encryptor>(m, "Encryptor", SEAL_DOC("Encrypts plaintexts using a public key or a secret key.")) + .def(py::init<const SEALContext &, const PublicKey &>(), py::arg("context"), py::arg("public_key"), + SEAL_DOC("Create an encryptor configured for public-key encryption.")) + .def(py::init<const SEALContext &, const SecretKey &>(), py::arg("context"), py::arg("secret_key"), + SEAL_DOC("Create an encryptor configured for secret-key encryption.")) + .def(py::init<const SEALContext &, const PublicKey &, const SecretKey &>(), + py::arg("context"), py::arg("public_key"), py::arg("secret_key"), + SEAL_DOC("Create an encryptor configured with both public and secret keys.")) + .def("set_public_key", &Encryptor::set_public_key, py::arg("public_key"), + SEAL_DOC("Set or replace the public key used for encryption.")) + .def("set_secret_key", &Encryptor::set_secret_key, py::arg("secret_key"), + SEAL_DOC("Set or replace the secret key used for symmetric encryption.")) .def("encrypt_zero", [](const Encryptor &encryptor){ Ciphertext encrypted; encryptor.encrypt_zero(encrypted); return encrypted; - }) + }, SEAL_DOC("Encrypt the zero plaintext at the first data level and return the ciphertext.")) .def("encrypt_zero", [](const Encryptor &encryptor, Ciphertext &destination){ encryptor.encrypt_zero(destination); - }) + }, py::arg("destination"), + SEAL_DOC("Encrypt the zero plaintext at the first data level into destination.")) .def("encrypt_zero", [](const Encryptor &encryptor, parms_id_type parms_id){ Ciphertext encrypted; encryptor.encrypt_zero(parms_id, encrypted); return encrypted; - }) + }, py::arg("parms_id"), + SEAL_DOC("Encrypt the zero plaintext for the specified parms_id and return the ciphertext.")) .def("encrypt_zero", [](const Encryptor &encryptor, parms_id_type parms_id, Ciphertext &destination){ encryptor.encrypt_zero(parms_id, destination); - }) + }, py::arg("parms_id"), py::arg("destination"), + SEAL_DOC("Encrypt the zero plaintext for the specified parms_id into destination.")) .def("encrypt", [](const Encryptor &encryptor, const Plaintext &plain){ Ciphertext encrypted; encryptor.encrypt(plain, encrypted); return encrypted; - }) + }, py::arg("plain"), + SEAL_DOC("Encrypt a plaintext with the public key and return the ciphertext.")) .def("encrypt", [](const Encryptor &encryptor, const Plaintext &plain, Ciphertext &destination){ encryptor.encrypt(plain, destination); - }) + }, py::arg("plain"), py::arg("destination"), + SEAL_DOC("Encrypt a plaintext with the public key into destination.")) .def("encrypt_symmetric", [](const Encryptor &encryptor, const Plaintext &plain){ Ciphertext encrypted; encryptor.encrypt_symmetric(plain, encrypted); return encrypted; - }) + }, py::arg("plain"), + SEAL_DOC("Encrypt a plaintext with the secret key and return the ciphertext.")) .def("encrypt_symmetric", [](const Encryptor &encryptor, const Plaintext &plain, Ciphertext &destination){ encryptor.encrypt_symmetric(plain, destination); - }); + }, py::arg("plain"), py::arg("destination"), + SEAL_DOC("Encrypt a plaintext with the secret key into destination.")); // evaluator.h - py::class_<Evaluator>(m, "Evaluator") - .def(py::init<const SEALContext &>()) - .def("negate_inplace", &Evaluator::negate_inplace) + py::class_<Evaluator>(m, "Evaluator", SEAL_DOC("Applies homomorphic operations to ciphertexts and plaintexts.")) + .def(py::init<const SEALContext &>(), py::arg("context"), + SEAL_DOC("Create an evaluator for ciphertext operations under the given context.")) + .def("negate_inplace", &Evaluator::negate_inplace, py::arg("encrypted"), + SEAL_DOC("Negate a ciphertext in place.")) .def("negate", [](Evaluator &evaluator, const Ciphertext &encrypted1){ Ciphertext destination; evaluator.negate(encrypted1, destination); return destination; - }) - .def("add_inplace", &Evaluator::add_inplace) + }, py::arg("encrypted"), + SEAL_DOC("Negate a ciphertext and return the result.")) + .def("add_inplace", &Evaluator::add_inplace, py::arg("encrypted1"), py::arg("encrypted2"), + SEAL_DOC("Add two ciphertexts and store the result in encrypted1.")) .def("add", [](Evaluator &evaluator, const Ciphertext &encrypted1, const Ciphertext &encrypted2){ Ciphertext destination; evaluator.add(encrypted1, encrypted2, destination); return destination; - }) + }, py::arg("encrypted1"), py::arg("encrypted2"), + SEAL_DOC("Add two ciphertexts and return the result.")) .def("add_many", [](Evaluator &evaluator, const std::vector<Ciphertext> &encrypteds){ Ciphertext destination; evaluator.add_many(encrypteds, destination); return destination; - }) - .def("sub_inplace", &Evaluator::sub_inplace) + }, py::arg("encrypteds"), + SEAL_DOC("Add many ciphertexts together and return the sum.")) + .def("sub_inplace", &Evaluator::sub_inplace, py::arg("encrypted1"), py::arg("encrypted2"), + SEAL_DOC("Subtract encrypted2 from encrypted1 in place.")) .def("sub", [](Evaluator &evaluator, const Ciphertext &encrypted1, const Ciphertext &encrypted2){ Ciphertext destination; evaluator.sub(encrypted1, encrypted2, destination); return destination; - }) + }, py::arg("encrypted1"), py::arg("encrypted2"), + SEAL_DOC("Subtract two ciphertexts and return the result.")) .def("multiply_inplace", [](Evaluator &evaluator, Ciphertext &encrypted1, const Ciphertext &encrypted2){ evaluator.multiply_inplace(encrypted1, encrypted2); - }) + }, py::arg("encrypted1"), py::arg("encrypted2"), + SEAL_DOC("Multiply two ciphertexts and store the result in encrypted1.")) .def("multiply", [](Evaluator &evaluator, const Ciphertext &encrypted1, const Ciphertext &encrypted2){ Ciphertext destination; evaluator.multiply(encrypted1, encrypted2, destination); return destination; - }) + }, py::arg("encrypted1"), py::arg("encrypted2"), + SEAL_DOC("Multiply two ciphertexts and return the result.")) .def("square_inplace", [](Evaluator &evaluator, Ciphertext &encrypted1){ evaluator.square_inplace(encrypted1); - }) + }, py::arg("encrypted"), + SEAL_DOC("Square a ciphertext in place.")) .def("square", [](Evaluator &evaluator, const Ciphertext &encrypted1){ Ciphertext destination; evaluator.square(encrypted1, destination); return destination; - }) + }, py::arg("encrypted"), + SEAL_DOC("Square a ciphertext and return the result.")) .def("relinearize_inplace", [](Evaluator &evaluator, Ciphertext &encrypted1, const RelinKeys &relin_keys){ evaluator.relinearize_inplace(encrypted1, relin_keys); - }) + }, py::arg("encrypted"), py::arg("relin_keys"), + SEAL_DOC("Relinearize a ciphertext in place using relinearization keys.")) .def("relinearize", [](Evaluator &evaluator, const Ciphertext &encrypted1, const RelinKeys &relin_keys){ Ciphertext destination; evaluator.relinearize(encrypted1, relin_keys, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("relin_keys"), + SEAL_DOC("Relinearize a ciphertext and return the result.")) .def("mod_switch_to_next", [](Evaluator &evaluator, const Ciphertext &encrypted){ Ciphertext destination; evaluator.mod_switch_to_next(encrypted, destination); return destination; - }) + }, py::arg("encrypted"), + SEAL_DOC("Mod-switch a ciphertext to the next level in the modulus chain and return the result.")) .def("mod_switch_to_next_inplace", [](Evaluator &evaluator, Ciphertext &encrypted){ evaluator.mod_switch_to_next_inplace(encrypted); - }) - .def("mod_switch_to_next_inplace", py::overload_cast<Plaintext &>(&Evaluator::mod_switch_to_next_inplace, py::const_)) + }, py::arg("encrypted"), + SEAL_DOC("Mod-switch a ciphertext to the next level in place.")) + .def("mod_switch_to_next_inplace", py::overload_cast<Plaintext &>(&Evaluator::mod_switch_to_next_inplace, py::const_), + py::arg("plain"), + SEAL_DOC("Mod-switch a plaintext to the next level in place.")) .def("mod_switch_to_next", [](Evaluator &evaluator, const Plaintext &plain){ Plaintext destination; evaluator.mod_switch_to_next(plain, destination); return destination; - }) + }, py::arg("plain"), + SEAL_DOC("Mod-switch a plaintext to the next level and return the result.")) .def("mod_switch_to_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, parms_id_type parms_id){ evaluator.mod_switch_to_inplace(encrypted, parms_id); - }) + }, py::arg("encrypted"), py::arg("parms_id"), + SEAL_DOC("Mod-switch a ciphertext in place to the specified parms_id.")) .def("mod_switch_to", [](Evaluator &evaluator, const Ciphertext &encrypted, parms_id_type parms_id){ Ciphertext destination; evaluator.mod_switch_to(encrypted, parms_id, destination); return destination; - }) - .def("mod_switch_to_inplace", py::overload_cast<Plaintext &, parms_id_type>(&Evaluator::mod_switch_to_inplace, py::const_)) + }, py::arg("encrypted"), py::arg("parms_id"), + SEAL_DOC("Mod-switch a ciphertext to the specified parms_id and return the result.")) + .def("mod_switch_to_inplace", py::overload_cast<Plaintext &, parms_id_type>(&Evaluator::mod_switch_to_inplace, py::const_), + py::arg("plain"), py::arg("parms_id"), + SEAL_DOC("Mod-switch a plaintext in place to the specified parms_id.")) .def("mod_switch_to", [](Evaluator &evaluator, const Plaintext &plain, parms_id_type parms_id){ Plaintext destination; evaluator.mod_switch_to(plain, parms_id, destination); return destination; - }) + }, py::arg("plain"), py::arg("parms_id"), + SEAL_DOC("Mod-switch a plaintext to the specified parms_id and return the result.")) .def("rescale_to_next", [](Evaluator &evaluator, const Ciphertext &encrypted){ Ciphertext destination; evaluator.rescale_to_next(encrypted, destination); return destination; - }) + }, py::arg("encrypted"), + SEAL_DOC("Rescale a CKKS ciphertext to the next level and return the result.")) .def("rescale_to_next_inplace", [](Evaluator &evaluator, Ciphertext &encrypted){ evaluator.rescale_to_next_inplace(encrypted); - }) + }, py::arg("encrypted"), + SEAL_DOC("Rescale a CKKS ciphertext to the next level in place.")) .def("rescale_to_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, parms_id_type parms_id){ evaluator.rescale_to_inplace(encrypted, parms_id); - }) + }, py::arg("encrypted"), py::arg("parms_id"), + SEAL_DOC("Rescale a CKKS ciphertext in place to the specified parms_id.")) .def("rescale_to", [](Evaluator &evaluator, const Ciphertext &encrypted, parms_id_type parms_id){ Ciphertext destination; evaluator.rescale_to(encrypted, parms_id, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("parms_id"), + SEAL_DOC("Rescale a CKKS ciphertext to the specified parms_id and return the result.")) .def("multiply_many", [](Evaluator &evaluator, const std::vector<Ciphertext> &encrypteds, const RelinKeys &relin_keys){ Ciphertext destination; evaluator.multiply_many(encrypteds, relin_keys, destination); return destination; - }) + }, py::arg("encrypteds"), py::arg("relin_keys"), + SEAL_DOC("Multiply many ciphertexts together and return the result.")) .def("exponentiate_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, std::uint64_t exponent, const RelinKeys &relin_keys){ evaluator.exponentiate_inplace(encrypted, exponent, relin_keys); - }) + }, py::arg("encrypted"), py::arg("exponent"), py::arg("relin_keys"), + SEAL_DOC("Raise a ciphertext to a power in place using repeated multiplication and relinearization.")) .def("exponentiate", [](Evaluator &evaluator, const Ciphertext &encrypted, std::uint64_t exponent, const RelinKeys &relin_keys){ Ciphertext destination; evaluator.exponentiate(encrypted, exponent, relin_keys, destination); return destination; - }) - .def("add_plain_inplace", &Evaluator::add_plain_inplace) + }, py::arg("encrypted"), py::arg("exponent"), py::arg("relin_keys"), + SEAL_DOC("Raise a ciphertext to a power and return the result.")) + .def("add_plain_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, const Plaintext &plain){ + evaluator.add_plain_inplace(encrypted, plain); + }, py::arg("encrypted"), py::arg("plain"), + SEAL_DOC("Add a plaintext to a ciphertext in place.")) .def("add_plain", [](Evaluator &evaluator, const Ciphertext &encrypted, const Plaintext &plain){ Ciphertext destination; evaluator.add_plain(encrypted, plain, destination); return destination; - }) - .def("sub_plain_inplace", &Evaluator::sub_plain_inplace) + }, py::arg("encrypted"), py::arg("plain"), + SEAL_DOC("Add a plaintext to a ciphertext and return the result.")) + .def("sub_plain_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, const Plaintext &plain){ + evaluator.sub_plain_inplace(encrypted, plain); + }, py::arg("encrypted"), py::arg("plain"), + SEAL_DOC("Subtract a plaintext from a ciphertext in place.")) .def("sub_plain", [](Evaluator &evaluator, const Ciphertext &encrypted, const Plaintext &plain){ Ciphertext destination; evaluator.sub_plain(encrypted, plain, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("plain"), + SEAL_DOC("Subtract a plaintext from a ciphertext and return the result.")) .def("multiply_plain_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, const Plaintext &plain){ evaluator.multiply_plain_inplace(encrypted, plain); - }) + }, py::arg("encrypted"), py::arg("plain"), + SEAL_DOC("Multiply a ciphertext by a plaintext in place.")) .def("multiply_plain", [](Evaluator &evaluator, const Ciphertext &encrypted, const Plaintext &plain){ Ciphertext destination; evaluator.multiply_plain(encrypted, plain, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("plain"), + SEAL_DOC("Multiply a ciphertext by a plaintext and return the result.")) .def("transform_to_ntt_inplace", [](Evaluator &evaluator, Plaintext &plain, parms_id_type parms_id){ evaluator.transform_to_ntt_inplace(plain,parms_id); - }) + }, py::arg("plain"), py::arg("parms_id"), + SEAL_DOC("Transform a plaintext to NTT form in place.")) .def("transform_to_ntt", [](Evaluator &evaluator, const Plaintext &plain, parms_id_type parms_id){ Plaintext destination_ntt; evaluator.transform_to_ntt(plain, parms_id, destination_ntt); return destination_ntt; - }) - .def("transform_to_ntt_inplace", py::overload_cast<Ciphertext &>(&Evaluator::transform_to_ntt_inplace, py::const_)) + }, py::arg("plain"), py::arg("parms_id"), + SEAL_DOC("Transform a plaintext to NTT form and return the result.")) + .def("transform_to_ntt_inplace", py::overload_cast<Ciphertext &>(&Evaluator::transform_to_ntt_inplace, py::const_), + py::arg("encrypted"), + SEAL_DOC("Transform a ciphertext to NTT form in place.")) .def("transform_to_ntt", [](Evaluator &evaluator, const Ciphertext &encrypted){ Ciphertext destination_ntt; evaluator.transform_to_ntt(encrypted, destination_ntt); return destination_ntt; - }) - .def("transform_from_ntt_inplace", &Evaluator::transform_from_ntt_inplace) + }, py::arg("encrypted"), + SEAL_DOC("Transform a ciphertext to NTT form and return the result.")) + .def("transform_from_ntt_inplace", &Evaluator::transform_from_ntt_inplace, py::arg("encrypted_ntt"), + SEAL_DOC("Transform an NTT-form ciphertext back to coefficient form in place.")) .def("transform_from_ntt", [](Evaluator &evaluator, const Ciphertext &encrypted_ntt){ Ciphertext destination; evaluator.transform_from_ntt(encrypted_ntt, destination); return destination; - }) + }, py::arg("encrypted_ntt"), + SEAL_DOC("Transform an NTT-form ciphertext back to coefficient form and return the result.")) .def("apply_galois_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, std::uint32_t galois_elt, const GaloisKeys &galois_keys){ evaluator.apply_galois_inplace(encrypted, galois_elt, galois_keys); - }) + }, py::arg("encrypted"), py::arg("galois_elt"), py::arg("galois_keys"), + SEAL_DOC("Apply a Galois automorphism to a ciphertext in place.")) .def("apply_galois", [](Evaluator &evaluator, const Ciphertext &encrypted, std::uint32_t galois_elt, const GaloisKeys &galois_keys){ Ciphertext destination; evaluator.apply_galois(encrypted, galois_elt, galois_keys, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("galois_elt"), py::arg("galois_keys"), + SEAL_DOC("Apply a Galois automorphism to a ciphertext and return the result.")) .def("rotate_rows_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, int steps, const GaloisKeys &galois_keys){ evaluator.rotate_rows_inplace(encrypted, steps, galois_keys); - }) + }, py::arg("encrypted"), py::arg("steps"), py::arg("galois_keys"), + SEAL_DOC("Rotate BFV/BGV batching rows in place.")) .def("rotate_rows", [](Evaluator &evaluator, const Ciphertext &encrypted, int steps, const GaloisKeys &galois_keys){ Ciphertext destination; evaluator.rotate_rows(encrypted, steps, galois_keys, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("steps"), py::arg("galois_keys"), + SEAL_DOC("Rotate BFV/BGV batching rows and return the result.")) .def("rotate_columns_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, const GaloisKeys &galois_keys){ evaluator.rotate_columns_inplace(encrypted, galois_keys); - }) + }, py::arg("encrypted"), py::arg("galois_keys"), + SEAL_DOC("Rotate BFV/BGV batching columns in place.")) .def("rotate_columns", [](Evaluator &evaluator, const Ciphertext &encrypted, const GaloisKeys &galois_keys){ Ciphertext destination; evaluator.rotate_columns(encrypted, galois_keys, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("galois_keys"), + SEAL_DOC("Rotate BFV/BGV batching columns and return the result.")) .def("rotate_vector_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, int steps, const GaloisKeys &galois_keys){ evaluator.rotate_vector_inplace(encrypted, steps, galois_keys); - }) + }, py::arg("encrypted"), py::arg("steps"), py::arg("galois_keys"), + SEAL_DOC("Rotate a CKKS vector in place.")) .def("rotate_vector", [](Evaluator &evaluator, const Ciphertext &encrypted, int steps, const GaloisKeys &galois_keys){ Ciphertext destination; evaluator.rotate_vector(encrypted, steps, galois_keys, destination); return destination; - }) + }, py::arg("encrypted"), py::arg("steps"), py::arg("galois_keys"), + SEAL_DOC("Rotate a CKKS vector and return the result.")) .def("complex_conjugate_inplace", [](Evaluator &evaluator, Ciphertext &encrypted, const GaloisKeys &galois_keys){ evaluator.complex_conjugate_inplace(encrypted, galois_keys); - }) + }, py::arg("encrypted"), py::arg("galois_keys"), + SEAL_DOC("Apply CKKS complex conjugation in place.")) .def("complex_conjugate", [](Evaluator &evaluator, const Ciphertext &encrypted, const GaloisKeys &galois_keys){ Ciphertext destination; evaluator.complex_conjugate(encrypted, galois_keys, destination); return destination; - }); + }, py::arg("encrypted"), py::arg("galois_keys"), + SEAL_DOC("Apply CKKS complex conjugation and return the result.")); // ckks.h - py::class_<CKKSEncoder>(m, "CKKSEncoder") - .def(py::init<const SEALContext &>()) - .def("slot_count", &CKKSEncoder::slot_count) + py::class_<CKKSEncoder>(m, "CKKSEncoder", SEAL_DOC("Encodes floating-point and complex vectors into CKKS plaintext polynomials.")) + .def(py::init<const SEALContext &>(), py::arg("context"), + SEAL_DOC("Create a CKKS encoder for the given context.")) + .def("slot_count", &CKKSEncoder::slot_count, SEAL_DOC("Return the number of SIMD slots available for CKKS encoding.")) .def("encode_complex", [](CKKSEncoder &encoder, const std::vector<std::complex<double>> &values, double scale, Plaintext &destination){ encoder.encode(values, scale, destination); - }) + }, py::arg("values"), py::arg("scale"), py::arg("destination"), + SEAL_DOC("Encode a vector of complex values into destination.")) .def("encode", [](CKKSEncoder &encoder, const std::vector<double> &values, double scale, Plaintext &destination){ encoder.encode(values, scale, destination); - }) + }, py::arg("values"), py::arg("scale"), py::arg("destination"), + SEAL_DOC("Encode a vector of real values into destination.")) .def("encode_complex", [](CKKSEncoder &encoder, py::array_t<std::complex<double>> values, double scale){ py::buffer_info buf = values.request(); if (buf.ndim == 0) @@ -735,7 +945,8 @@ PYBIND11_MODULE(seal, m) Plaintext pt; encoder.encode(vec, scale, pt); return pt; - }) + }, py::arg("values"), py::arg("scale"), + SEAL_DOC("Encode a NumPy array or scalar of complex values and return the plaintext.")) .def("encode_complex", [](CKKSEncoder &encoder, py::array_t<std::complex<double>> values, double scale, Plaintext &destination){ py::buffer_info buf = values.request(); if (buf.ndim == 0) @@ -754,7 +965,8 @@ PYBIND11_MODULE(seal, m) vec[static_cast<std::size_t>(i)] = ptr[i]; encoder.encode(vec, scale, destination); - }) + }, py::arg("values"), py::arg("scale"), py::arg("destination"), + SEAL_DOC("Encode a NumPy array or scalar of complex values into destination.")) .def("encode", [](CKKSEncoder &encoder, py::array_t<double> values, double scale){ py::buffer_info buf = values.request(); if (buf.ndim != 1) @@ -769,7 +981,8 @@ PYBIND11_MODULE(seal, m) Plaintext pt; encoder.encode(vec, scale, pt); return pt; - }) + }, py::arg("values"), py::arg("scale"), + SEAL_DOC("Encode a one-dimensional NumPy array of real values and return the plaintext.")) .def("encode_complex", [](CKKSEncoder &encoder, py::iterable values, double scale){ std::vector<std::complex<double>> vec; vec.reserve(py::len(values)); @@ -779,14 +992,16 @@ PYBIND11_MODULE(seal, m) Plaintext pt; encoder.encode(vec, scale, pt); return pt; - }) + }, py::arg("values"), py::arg("scale"), + SEAL_DOC("Encode an iterable of complex values and return the plaintext.")) .def("encode_complex", [](CKKSEncoder &encoder, py::iterable values, double scale, Plaintext &destination){ std::vector<std::complex<double>> vec; vec.reserve(py::len(values)); for (const auto &value : values) vec.push_back(py::cast<std::complex<double>>(value)); encoder.encode(vec, scale, destination); - }) + }, py::arg("values"), py::arg("scale"), py::arg("destination"), + SEAL_DOC("Encode an iterable of complex values into destination.")) .def("encode", [](CKKSEncoder &encoder, py::iterable values, double scale){ std::vector<double> vec; vec.reserve(py::len(values)); @@ -796,38 +1011,46 @@ PYBIND11_MODULE(seal, m) Plaintext pt; encoder.encode(vec, scale, pt); return pt; - }) + }, py::arg("values"), py::arg("scale"), + SEAL_DOC("Encode an iterable of real values and return the plaintext.")) .def("encode", [](CKKSEncoder &encoder, py::iterable values, double scale, Plaintext &destination){ std::vector<double> vec; vec.reserve(py::len(values)); for (const auto &value : values) vec.push_back(py::cast<double>(value)); encoder.encode(vec, scale, destination); - }) + }, py::arg("values"), py::arg("scale"), py::arg("destination"), + SEAL_DOC("Encode an iterable of real values into destination.")) .def("encode", [](CKKSEncoder &encoder, double value, double scale){ Plaintext pt; encoder.encode(value, scale, pt); return pt; - }) + }, py::arg("value"), py::arg("scale"), + SEAL_DOC("Encode a single real value and return the plaintext.")) .def("encode", [](CKKSEncoder &encoder, double value, double scale, Plaintext &destination){ encoder.encode(value, scale, destination); - }) + }, py::arg("value"), py::arg("scale"), py::arg("destination"), + SEAL_DOC("Encode a single real value into destination.")) .def("encode_complex", [](CKKSEncoder &encoder, std::complex<double> value, double scale){ Plaintext pt; encoder.encode(value, scale, pt); return pt; - }) + }, py::arg("value"), py::arg("scale"), + SEAL_DOC("Encode a single complex value and return the plaintext.")) .def("encode_complex", [](CKKSEncoder &encoder, std::complex<double> value, double scale, Plaintext &destination){ encoder.encode(value, scale, destination); - }) + }, py::arg("value"), py::arg("scale"), py::arg("destination"), + SEAL_DOC("Encode a single complex value into destination.")) .def("encode", [](CKKSEncoder &encoder, std::int64_t value){ Plaintext pt; encoder.encode(value, pt); return pt; - }) + }, py::arg("value"), + SEAL_DOC("Encode a signed integer exactly into a CKKS plaintext.")) .def("encode", [](CKKSEncoder &encoder, std::int64_t value, Plaintext &destination){ encoder.encode(value, destination); - }) + }, py::arg("value"), py::arg("destination"), + SEAL_DOC("Encode a signed integer exactly into destination.")) .def("decode", [](CKKSEncoder &encoder, const Plaintext &plain){ std::vector<double> destination; encoder.decode(plain, destination); @@ -840,7 +1063,8 @@ PYBIND11_MODULE(seal, m) ptr[i] = destination[i]; return values; - }) + }, py::arg("plain"), + SEAL_DOC("Decode a CKKS plaintext into a NumPy array of real values.")) .def("decode_complex", [](CKKSEncoder &encoder, const Plaintext &plain){ std::vector<std::complex<double>> destination; encoder.decode(plain, destination); @@ -853,29 +1077,37 @@ PYBIND11_MODULE(seal, m) ptr[i] = destination[static_cast<std::size_t>(i)]; return values; - }); + }, py::arg("plain"), + SEAL_DOC("Decode a CKKS plaintext into a NumPy array of complex values.")); // decryptor.h - py::class_<Decryptor>(m, "Decryptor") - .def(py::init<const SEALContext &, const SecretKey &>()) - .def("decrypt", &Decryptor::decrypt) - .def("invariant_noise_budget", &Decryptor::invariant_noise_budget) + py::class_<Decryptor>(m, "Decryptor", SEAL_DOC("Decrypts ciphertexts using the secret key and inspects their remaining noise budget.")) + .def(py::init<const SEALContext &, const SecretKey &>(), py::arg("context"), py::arg("secret_key"), + SEAL_DOC("Create a decryptor for the given context and secret key.")) + .def("decrypt", &Decryptor::decrypt, py::arg("encrypted"), py::arg("destination"), + SEAL_DOC("Decrypt a ciphertext into destination.")) + .def("invariant_noise_budget", &Decryptor::invariant_noise_budget, py::arg("encrypted"), + SEAL_DOC("Return the invariant noise budget of a ciphertext in bits.")) .def("decrypt", [](Decryptor &decryptor, const Ciphertext &encrypted){ Plaintext pt; decryptor.decrypt(encrypted, pt); return pt; - }); + }, py::arg("encrypted"), + SEAL_DOC("Decrypt a ciphertext and return the plaintext.")); // batchencoder.h - py::class_<BatchEncoder>(m, "BatchEncoder") - .def(py::init<const SEALContext &>()) - .def("slot_count", &BatchEncoder::slot_count) + py::class_<BatchEncoder>(m, "BatchEncoder", SEAL_DOC("Encodes integer vectors into BFV/BGV batching plaintexts and decodes them back.")) + .def(py::init<const SEALContext &>(), py::arg("context"), + SEAL_DOC("Create a batch encoder for the given context.")) + .def("slot_count", &BatchEncoder::slot_count, SEAL_DOC("Return the number of batching slots available.")) .def("encode", [](BatchEncoder &encoder, const std::vector<std::int64_t> &values, Plaintext &destination){ encoder.encode(values, destination); - }) + }, py::arg("values"), py::arg("destination"), + SEAL_DOC("Encode a vector of signed integers into destination.")) .def("encode", [](BatchEncoder &encoder, const std::vector<std::uint64_t> &values, Plaintext &destination){ encoder.encode(values, destination); - }) + }, py::arg("values"), py::arg("destination"), + SEAL_DOC("Encode a vector of unsigned integers into destination.")) .def("encode", [](BatchEncoder &encoder, py::array_t<std::int64_t> values){ py::buffer_info buf = values.request(); if (buf.ndim != 1) @@ -890,7 +1122,8 @@ PYBIND11_MODULE(seal, m) Plaintext pt; encoder.encode(vec, pt); return pt; - }) + }, py::arg("values"), + SEAL_DOC("Encode a one-dimensional NumPy array of signed integers and return the plaintext.")) .def("encode", [](BatchEncoder &encoder, py::array_t<std::uint64_t> values){ py::buffer_info buf = values.request(); if (buf.ndim != 1) @@ -905,7 +1138,8 @@ PYBIND11_MODULE(seal, m) Plaintext pt; encoder.encode(vec, pt); return pt; - }) + }, py::arg("values"), + SEAL_DOC("Encode a one-dimensional NumPy array of unsigned integers and return the plaintext.")) .def("encode", [](BatchEncoder &encoder, py::iterable values){ std::vector<std::int64_t> vec; vec.reserve(py::len(values)); @@ -915,7 +1149,8 @@ PYBIND11_MODULE(seal, m) Plaintext pt; encoder.encode(vec, pt); return pt; - }) + }, py::arg("values"), + SEAL_DOC("Encode an iterable of integers and return the plaintext.")) .def("decode_uint64", [](BatchEncoder &encoder, const Plaintext &plain){ std::vector<std::uint64_t> destination; encoder.decode(plain, destination); @@ -928,7 +1163,8 @@ PYBIND11_MODULE(seal, m) ptr[i] = destination[static_cast<std::size_t>(i)]; return values; - }) + }, py::arg("plain"), + SEAL_DOC("Decode a batched plaintext into a NumPy array of unsigned 64-bit integers.")) .def("decode", [](BatchEncoder &encoder, const Plaintext &plain){ std::vector<std::int64_t> destination; encoder.decode(plain, destination); @@ -941,5 +1177,6 @@ PYBIND11_MODULE(seal, m) ptr[i] = destination[i]; return values; - }); + }, py::arg("plain"), + SEAL_DOC("Decode a batched plaintext into a NumPy array of signed 64-bit integers.")); }