diff --git a/aegis/_loader.py b/aegis/_loader.py deleted file mode 100644 index e2fa909..0000000 --- a/aegis/_loader.py +++ /dev/null @@ -1,467 +0,0 @@ -"""Dynamic loader for libaegis using CFFI (ABI mode). - -This module avoids compiling any C shim. It loads the shared library built by -the project (e.g., build-shared/libaegis.so) or a system-installed libaegis. - -Environment variables: -- AEGIS_LIB_PATH: full path to the libaegis shared library to load -- AEGIS_LIB_DIR: directory containing the shared library (libaegis.so) - -Exports: -- ffi: a cffi.FFI instance with the libaegis API declared -- lib: the loaded libaegis shared library (ffi.dlopen) -- libc: the C runtime (for posix_memalign/free on POSIX) -- alloc_aligned(size, alignment): return (void*) pointer with requested alignment -""" - -import os -import sys -from typing import Any - -from cffi import FFI - -try: - # ctypes is only used to resolve libc reliably across platforms - import ctypes.util as _ctypes_util # type: ignore -except Exception: # pragma: no cover - very unlikely to happen - _ctypes_util = None # type: ignore[assignment] - - -ffi = FFI() - -# Public API from headers (aegis.h and all aegis variant headers). Keep it macro-free. -ffi.cdef( - r""" - typedef unsigned char uint8_t; - typedef unsigned long size_t; - - /* aegis.h */ - int aegis_init(void); - int aegis_verify_16(const uint8_t *x, const uint8_t *y); - int aegis_verify_32(const uint8_t *x, const uint8_t *y); - - /* aegis128l.h */ - typedef struct { - /* CRYPTO_ALIGN(32) */ uint8_t opaque[256]; - } aegis128l_state; - - typedef struct { - /* CRYPTO_ALIGN(32) */ uint8_t opaque[384]; - } aegis128l_mac_state; - - size_t aegis128l_keybytes(void); - size_t aegis128l_npubbytes(void); - size_t aegis128l_abytes_min(void); - size_t aegis128l_abytes_max(void); - size_t aegis128l_tailbytes_max(void); - - int aegis128l_encrypt_detached(uint8_t *c, uint8_t *mac, size_t maclen, const uint8_t *m, - size_t mlen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis128l_decrypt_detached(uint8_t *m, const uint8_t *c, size_t clen, const uint8_t *mac, - size_t maclen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis128l_encrypt(uint8_t *c, size_t maclen, const uint8_t *m, size_t mlen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - int aegis128l_decrypt(uint8_t *m, const uint8_t *c, size_t clen, size_t maclen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - void aegis128l_state_init(aegis128l_state *st_, const uint8_t *ad, size_t adlen, - const uint8_t *npub, const uint8_t *k); - - int aegis128l_state_encrypt_update(aegis128l_state *st_, uint8_t *c, size_t clen_max, - size_t *written, const uint8_t *m, size_t mlen); - - int aegis128l_state_encrypt_detached_final(aegis128l_state *st_, uint8_t *c, size_t clen_max, - size_t *written, uint8_t *mac, size_t maclen); - - int aegis128l_state_encrypt_final(aegis128l_state *st_, uint8_t *c, size_t clen_max, - size_t *written, size_t maclen); - - int aegis128l_state_decrypt_detached_update(aegis128l_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *c, size_t clen); - - int aegis128l_state_decrypt_detached_final(aegis128l_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *mac, size_t maclen); - - void aegis128l_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k); - void aegis128l_encrypt_unauthenticated(uint8_t *c, const uint8_t *m, size_t mlen, - const uint8_t *npub, const uint8_t *k); - void aegis128l_decrypt_unauthenticated(uint8_t *m, const uint8_t *c, size_t clen, - const uint8_t *npub, const uint8_t *k); - - void aegis128l_mac_init(aegis128l_mac_state *st_, const uint8_t *k, const uint8_t *npub); - int aegis128l_mac_update(aegis128l_mac_state *st_, const uint8_t *m, size_t mlen); - int aegis128l_mac_final(aegis128l_mac_state *st_, uint8_t *mac, size_t maclen); - int aegis128l_mac_verify(aegis128l_mac_state *st_, const uint8_t *mac, size_t maclen); - void aegis128l_mac_reset(aegis128l_mac_state *st_); - void aegis128l_mac_state_clone(aegis128l_mac_state *dst, const aegis128l_mac_state *src); - - /* aegis128x2.h */ - typedef struct { - /* CRYPTO_ALIGN(64) */ uint8_t opaque[448]; - } aegis128x2_state; - - typedef struct { - /* CRYPTO_ALIGN(64) */ uint8_t opaque[704]; - } aegis128x2_mac_state; - - size_t aegis128x2_keybytes(void); - size_t aegis128x2_npubbytes(void); - size_t aegis128x2_abytes_min(void); - size_t aegis128x2_abytes_max(void); - size_t aegis128x2_tailbytes_max(void); - - int aegis128x2_encrypt_detached(uint8_t *c, uint8_t *mac, size_t maclen, const uint8_t *m, - size_t mlen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis128x2_decrypt_detached(uint8_t *m, const uint8_t *c, size_t clen, const uint8_t *mac, - size_t maclen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis128x2_encrypt(uint8_t *c, size_t maclen, const uint8_t *m, size_t mlen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - int aegis128x2_decrypt(uint8_t *m, const uint8_t *c, size_t clen, size_t maclen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - void aegis128x2_state_init(aegis128x2_state *st_, const uint8_t *ad, size_t adlen, - const uint8_t *npub, const uint8_t *k); - - int aegis128x2_state_encrypt_update(aegis128x2_state *st_, uint8_t *c, size_t clen_max, - size_t *written, const uint8_t *m, size_t mlen); - - int aegis128x2_state_encrypt_detached_final(aegis128x2_state *st_, uint8_t *c, size_t clen_max, - size_t *written, uint8_t *mac, size_t maclen); - - int aegis128x2_state_encrypt_final(aegis128x2_state *st_, uint8_t *c, size_t clen_max, - size_t *written, size_t maclen); - - int aegis128x2_state_decrypt_detached_update(aegis128x2_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *c, size_t clen); - - int aegis128x2_state_decrypt_detached_final(aegis128x2_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *mac, size_t maclen); - - void aegis128x2_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k); - void aegis128x2_encrypt_unauthenticated(uint8_t *c, const uint8_t *m, size_t mlen, - const uint8_t *npub, const uint8_t *k); - void aegis128x2_decrypt_unauthenticated(uint8_t *m, const uint8_t *c, size_t clen, - const uint8_t *npub, const uint8_t *k); - - void aegis128x2_mac_init(aegis128x2_mac_state *st_, const uint8_t *k, const uint8_t *npub); - int aegis128x2_mac_update(aegis128x2_mac_state *st_, const uint8_t *m, size_t mlen); - int aegis128x2_mac_final(aegis128x2_mac_state *st_, uint8_t *mac, size_t maclen); - int aegis128x2_mac_verify(aegis128x2_mac_state *st_, const uint8_t *mac, size_t maclen); - void aegis128x2_mac_reset(aegis128x2_mac_state *st_); - void aegis128x2_mac_state_clone(aegis128x2_mac_state *dst, const aegis128x2_mac_state *src); - - /* aegis128x4.h */ - typedef struct { - /* CRYPTO_ALIGN(64) */ uint8_t opaque[832]; - } aegis128x4_state; - - typedef struct { - /* CRYPTO_ALIGN(64) */ uint8_t opaque[1344]; - } aegis128x4_mac_state; - - size_t aegis128x4_keybytes(void); - size_t aegis128x4_npubbytes(void); - size_t aegis128x4_abytes_min(void); - size_t aegis128x4_abytes_max(void); - size_t aegis128x4_tailbytes_max(void); - - int aegis128x4_encrypt_detached(uint8_t *c, uint8_t *mac, size_t maclen, const uint8_t *m, - size_t mlen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis128x4_decrypt_detached(uint8_t *m, const uint8_t *c, size_t clen, const uint8_t *mac, - size_t maclen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis128x4_encrypt(uint8_t *c, size_t maclen, const uint8_t *m, size_t mlen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - int aegis128x4_decrypt(uint8_t *m, const uint8_t *c, size_t clen, size_t maclen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - void aegis128x4_state_init(aegis128x4_state *st_, const uint8_t *ad, size_t adlen, - const uint8_t *npub, const uint8_t *k); - - int aegis128x4_state_encrypt_update(aegis128x4_state *st_, uint8_t *c, size_t clen_max, - size_t *written, const uint8_t *m, size_t mlen); - - int aegis128x4_state_encrypt_detached_final(aegis128x4_state *st_, uint8_t *c, size_t clen_max, - size_t *written, uint8_t *mac, size_t maclen); - - int aegis128x4_state_encrypt_final(aegis128x4_state *st_, uint8_t *c, size_t clen_max, - size_t *written, size_t maclen); - - int aegis128x4_state_decrypt_detached_update(aegis128x4_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *c, size_t clen); - - int aegis128x4_state_decrypt_detached_final(aegis128x4_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *mac, size_t maclen); - - void aegis128x4_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k); - void aegis128x4_encrypt_unauthenticated(uint8_t *c, const uint8_t *m, size_t mlen, - const uint8_t *npub, const uint8_t *k); - void aegis128x4_decrypt_unauthenticated(uint8_t *m, const uint8_t *c, size_t clen, - const uint8_t *npub, const uint8_t *k); - - void aegis128x4_mac_init(aegis128x4_mac_state *st_, const uint8_t *k, const uint8_t *npub); - int aegis128x4_mac_update(aegis128x4_mac_state *st_, const uint8_t *m, size_t mlen); - int aegis128x4_mac_final(aegis128x4_mac_state *st_, uint8_t *mac, size_t maclen); - int aegis128x4_mac_verify(aegis128x4_mac_state *st_, const uint8_t *mac, size_t maclen); - void aegis128x4_mac_reset(aegis128x4_mac_state *st_); - void aegis128x4_mac_state_clone(aegis128x4_mac_state *dst, const aegis128x4_mac_state *src); - - /* aegis256.h */ - typedef struct { - /* CRYPTO_ALIGN(16) */ uint8_t opaque[192]; - } aegis256_state; - - typedef struct { - /* CRYPTO_ALIGN(16) */ uint8_t opaque[288]; - } aegis256_mac_state; - - size_t aegis256_keybytes(void); - size_t aegis256_npubbytes(void); - size_t aegis256_abytes_min(void); - size_t aegis256_abytes_max(void); - size_t aegis256_tailbytes_max(void); - - int aegis256_encrypt_detached(uint8_t *c, uint8_t *mac, size_t maclen, const uint8_t *m, - size_t mlen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis256_decrypt_detached(uint8_t *m, const uint8_t *c, size_t clen, const uint8_t *mac, - size_t maclen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis256_encrypt(uint8_t *c, size_t maclen, const uint8_t *m, size_t mlen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - int aegis256_decrypt(uint8_t *m, const uint8_t *c, size_t clen, size_t maclen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - void aegis256_state_init(aegis256_state *st_, const uint8_t *ad, size_t adlen, - const uint8_t *npub, const uint8_t *k); - - int aegis256_state_encrypt_update(aegis256_state *st_, uint8_t *c, size_t clen_max, - size_t *written, const uint8_t *m, size_t mlen); - - int aegis256_state_encrypt_detached_final(aegis256_state *st_, uint8_t *c, size_t clen_max, - size_t *written, uint8_t *mac, size_t maclen); - - int aegis256_state_encrypt_final(aegis256_state *st_, uint8_t *c, size_t clen_max, - size_t *written, size_t maclen); - - int aegis256_state_decrypt_detached_update(aegis256_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *c, size_t clen); - - int aegis256_state_decrypt_detached_final(aegis256_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *mac, size_t maclen); - - void aegis256_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k); - void aegis256_encrypt_unauthenticated(uint8_t *c, const uint8_t *m, size_t mlen, - const uint8_t *npub, const uint8_t *k); - void aegis256_decrypt_unauthenticated(uint8_t *m, const uint8_t *c, size_t clen, - const uint8_t *npub, const uint8_t *k); - - void aegis256_mac_init(aegis256_mac_state *st_, const uint8_t *k, const uint8_t *npub); - int aegis256_mac_update(aegis256_mac_state *st_, const uint8_t *m, size_t mlen); - int aegis256_mac_final(aegis256_mac_state *st_, uint8_t *mac, size_t maclen); - int aegis256_mac_verify(aegis256_mac_state *st_, const uint8_t *mac, size_t maclen); - void aegis256_mac_reset(aegis256_mac_state *st_); - void aegis256_mac_state_clone(aegis256_mac_state *dst, const aegis256_mac_state *src); - - /* aegis256x2.h */ - typedef struct { - /* CRYPTO_ALIGN(32) */ uint8_t opaque[320]; - } aegis256x2_state; - - typedef struct { - /* CRYPTO_ALIGN(32) */ uint8_t opaque[512]; - } aegis256x2_mac_state; - - size_t aegis256x2_keybytes(void); - size_t aegis256x2_npubbytes(void); - size_t aegis256x2_abytes_min(void); - size_t aegis256x2_abytes_max(void); - size_t aegis256x2_tailbytes_max(void); - - int aegis256x2_encrypt_detached(uint8_t *c, uint8_t *mac, size_t maclen, const uint8_t *m, - size_t mlen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis256x2_decrypt_detached(uint8_t *m, const uint8_t *c, size_t clen, const uint8_t *mac, - size_t maclen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis256x2_encrypt(uint8_t *c, size_t maclen, const uint8_t *m, size_t mlen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - int aegis256x2_decrypt(uint8_t *m, const uint8_t *c, size_t clen, size_t maclen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - void aegis256x2_state_init(aegis256x2_state *st_, const uint8_t *ad, size_t adlen, - const uint8_t *npub, const uint8_t *k); - - int aegis256x2_state_encrypt_update(aegis256x2_state *st_, uint8_t *c, size_t clen_max, - size_t *written, const uint8_t *m, size_t mlen); - - int aegis256x2_state_encrypt_detached_final(aegis256x2_state *st_, uint8_t *c, size_t clen_max, - size_t *written, uint8_t *mac, size_t maclen); - - int aegis256x2_state_encrypt_final(aegis256x2_state *st_, uint8_t *c, size_t clen_max, - size_t *written, size_t maclen); - - int aegis256x2_state_decrypt_detached_update(aegis256x2_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *c, size_t clen); - - int aegis256x2_state_decrypt_detached_final(aegis256x2_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *mac, size_t maclen); - - void aegis256x2_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k); - void aegis256x2_encrypt_unauthenticated(uint8_t *c, const uint8_t *m, size_t mlen, - const uint8_t *npub, const uint8_t *k); - void aegis256x2_decrypt_unauthenticated(uint8_t *m, const uint8_t *c, size_t clen, - const uint8_t *npub, const uint8_t *k); - - void aegis256x2_mac_init(aegis256x2_mac_state *st_, const uint8_t *k, const uint8_t *npub); - int aegis256x2_mac_update(aegis256x2_mac_state *st_, const uint8_t *m, size_t mlen); - int aegis256x2_mac_final(aegis256x2_mac_state *st_, uint8_t *mac, size_t maclen); - int aegis256x2_mac_verify(aegis256x2_mac_state *st_, const uint8_t *mac, size_t maclen); - void aegis256x2_mac_reset(aegis256x2_mac_state *st_); - void aegis256x2_mac_state_clone(aegis256x2_mac_state *dst, const aegis256x2_mac_state *src); - - /* aegis256x4.h */ - typedef struct { - /* CRYPTO_ALIGN(64) */ uint8_t opaque[576]; - } aegis256x4_state; - - typedef struct { - /* CRYPTO_ALIGN(64) */ uint8_t opaque[960]; - } aegis256x4_mac_state; - - size_t aegis256x4_keybytes(void); - size_t aegis256x4_npubbytes(void); - size_t aegis256x4_abytes_min(void); - size_t aegis256x4_abytes_max(void); - size_t aegis256x4_tailbytes_max(void); - - int aegis256x4_encrypt_detached(uint8_t *c, uint8_t *mac, size_t maclen, const uint8_t *m, - size_t mlen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis256x4_decrypt_detached(uint8_t *m, const uint8_t *c, size_t clen, const uint8_t *mac, - size_t maclen, const uint8_t *ad, size_t adlen, const uint8_t *npub, - const uint8_t *k); - - int aegis256x4_encrypt(uint8_t *c, size_t maclen, const uint8_t *m, size_t mlen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - int aegis256x4_decrypt(uint8_t *m, const uint8_t *c, size_t clen, size_t maclen, const uint8_t *ad, - size_t adlen, const uint8_t *npub, const uint8_t *k); - - void aegis256x4_state_init(aegis256x4_state *st_, const uint8_t *ad, size_t adlen, - const uint8_t *npub, const uint8_t *k); - - int aegis256x4_state_encrypt_update(aegis256x4_state *st_, uint8_t *c, size_t clen_max, - size_t *written, const uint8_t *m, size_t mlen); - - int aegis256x4_state_encrypt_detached_final(aegis256x4_state *st_, uint8_t *c, size_t clen_max, - size_t *written, uint8_t *mac, size_t maclen); - - int aegis256x4_state_encrypt_final(aegis256x4_state *st_, uint8_t *c, size_t clen_max, - size_t *written, size_t maclen); - - int aegis256x4_state_decrypt_detached_update(aegis256x4_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *c, size_t clen); - - int aegis256x4_state_decrypt_detached_final(aegis256x4_state *st_, uint8_t *m, size_t mlen_max, - size_t *written, const uint8_t *mac, size_t maclen); - - void aegis256x4_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k); - void aegis256x4_encrypt_unauthenticated(uint8_t *c, const uint8_t *m, size_t mlen, - const uint8_t *npub, const uint8_t *k); - void aegis256x4_decrypt_unauthenticated(uint8_t *m, const uint8_t *c, size_t clen, - const uint8_t *npub, const uint8_t *k); - - void aegis256x4_mac_init(aegis256x4_mac_state *st_, const uint8_t *k, const uint8_t *npub); - int aegis256x4_mac_update(aegis256x4_mac_state *st_, const uint8_t *m, size_t mlen); - int aegis256x4_mac_final(aegis256x4_mac_state *st_, uint8_t *mac, size_t maclen); - int aegis256x4_mac_verify(aegis256x4_mac_state *st_, const uint8_t *mac, size_t maclen); - void aegis256x4_mac_reset(aegis256x4_mac_state *st_); - void aegis256x4_mac_state_clone(aegis256x4_mac_state *dst, const aegis256x4_mac_state *src); - - /* libc bits for aligned allocation on POSIX */ - int posix_memalign(void **memptr, size_t alignment, size_t size); - void free(void *ptr); - """ -) - - -def _platform_lib_name() -> str: - if sys.platform.startswith("linux"): - return "libaegis.so" - if sys.platform == "darwin": - return "libaegis.dylib" - if os.name == "nt": - return "aegis.dll" - return "libaegis.so" - - -def _load_libaegis(): - # Let the dynamic loader search system paths - try: - lib = ffi.dlopen(_platform_lib_name()) - return lib - except Exception as e: - hint = "Install libaegis system-wide." - raise OSError(f"Could not load libaegis: {e}\n{hint}") - - -def _load_libc(): - # Use ctypes.util to find a usable libc name; fallback to None-dlopen on POSIX - if _ctypes_util is not None: - libc_name = _ctypes_util.find_library("c") # type: ignore[attr-defined] - if libc_name: - try: - return ffi.dlopen(libc_name) - except Exception: - pass - # Fallback: try process globals (works on many Unix platforms) - try: - return ffi.dlopen(None) - except Exception as e: - raise OSError(f"Unable to load libc for aligned allocation: {e}") - - -lib: Any = _load_libaegis() -libc: Any = _load_libc() - -# Initialize CPU feature selection (recommended by the library) -try: - lib.aegis_init() -except Exception: - # Non-fatal; functions will still work, maybe slower - pass - - -def alloc_aligned(size: int, alignment: int = 64): - """Allocate aligned memory via posix_memalign(); returns a void* cdata. - - The returned pointer must be freed with libc.free(). Attach a GC finalizer - at call sites using ffi.gc(ptr, libc.free) after casting to the target type. - """ - memptr = ffi.new("void **") - rc = libc.posix_memalign(memptr, alignment, size) - if rc != 0 or memptr[0] == ffi.NULL: - raise MemoryError(f"posix_memalign({alignment}, {size}) failed with rc={rc}") - return memptr[0] diff --git a/examples/aegis256x4_demo.py b/examples/aegis256x4_demo.py index 92ba895..e5f19ac 100644 --- a/examples/aegis256x4_demo.py +++ b/examples/aegis256x4_demo.py @@ -10,7 +10,7 @@ Covers: import time -from aegis import aegis256x4 as a +from pyaegis import aegis256x4 as a def hx(b, limit: int | None = None) -> str: diff --git a/examples/benchmark.py b/examples/benchmark.py index 912b639..09e4579 100644 --- a/examples/benchmark.py +++ b/examples/benchmark.py @@ -12,7 +12,7 @@ Output format and throughput units mirror the Zig benchmark (Mb/s). import os import time -from aegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4 +from pyaegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4 MSG_LEN = 16384000 # 16 MiB ITERATIONS = 100 diff --git a/aegis/__init__.py b/pyaegis/__init__.py similarity index 100% rename from aegis/__init__.py rename to pyaegis/__init__.py diff --git a/pyaegis/_loader.py b/pyaegis/_loader.py new file mode 100644 index 0000000..f693e3f --- /dev/null +++ b/pyaegis/_loader.py @@ -0,0 +1,90 @@ +"""Dynamic loader for libaegis using CFFI (ABI mode). + +This module loads the shared library bundled with the package. + +Exports: +- ffi: a cffi.FFI instance with the libaegis API declared +- lib: the loaded libaegis shared library (ffi.dlopen) +- libc: the C runtime (for posix_memalign/free on POSIX) +- alloc_aligned(size, alignment): return (void*) pointer with requested alignment +""" + +import os +import sys +from typing import Any + +from cffi import FFI + +ffi = FFI() + +# Load CFFI cdef declarations from text file +cdef_path = os.path.join(os.path.dirname(__file__), "build", "aegis_cdef.h") +with open(cdef_path, "r", encoding="utf-8") as f: + cdef_content = f.read() +ffi.cdef(cdef_content) + +try: + # ctypes is only used to resolve libc reliably across platforms + import ctypes.util as _ctypes_util # type: ignore +except Exception: # pragma: no cover - very unlikely to happen + _ctypes_util = None # type: ignore[assignment] + + +def _platform_lib_name() -> str: + if sys.platform == "darwin": + return "libaegis.dylib" + if os.name == "nt": + return "aegis.dll" + return "libaegis.so" + + +def _load_libaegis(): + pkg_dir = os.path.dirname(__file__) + candidate = os.path.join(pkg_dir, "build", _platform_lib_name()) + if os.path.exists(candidate): + try: + return ffi.dlopen(candidate) + except Exception as e: + raise OSError(f"Failed to load libaegis from {candidate}: {e}") + else: + raise OSError(f"Could not find libaegis at {candidate}") + + +def _load_libc(): + # Use ctypes.util to find a usable libc name; fallback to None-dlopen on POSIX + if _ctypes_util is not None: + libc_name = _ctypes_util.find_library("c") # type: ignore[attr-defined] + if libc_name: + try: + return ffi.dlopen(libc_name) + except Exception: + pass + # Fallback: try process globals (works on many Unix platforms) + try: + return ffi.dlopen(None) + except Exception as e: + raise OSError(f"Unable to load libc for aligned allocation: {e}") + + +lib: Any = _load_libaegis() +libc: Any = _load_libc() + +# Initialize CPU feature selection (recommended by the library) +try: + lib.aegis_init() +except Exception: + # Non-fatal; functions will still work, maybe slower + pass + + +def alloc_aligned(size: int, alignment: int = 64): + """Allocate aligned memory via posix_memalign(); returns a void* cdata. + + The returned pointer must be freed with libc.free(). Attach a GC finalizer + at call sites using ffi.gc(ptr, libc.free) after casting to the target type. + """ + memptr = ffi.new("void **") + rc = libc.posix_memalign(memptr, alignment, size) + if rc != 0 or memptr[0] == ffi.NULL: + raise MemoryError(f"posix_memalign({alignment}, {size}) failed with rc={rc}") + return memptr[0] diff --git a/aegis/aegis128l.py b/pyaegis/aegis128l.py similarity index 100% rename from aegis/aegis128l.py rename to pyaegis/aegis128l.py diff --git a/aegis/aegis128x2.py b/pyaegis/aegis128x2.py similarity index 100% rename from aegis/aegis128x2.py rename to pyaegis/aegis128x2.py diff --git a/aegis/aegis128x4.py b/pyaegis/aegis128x4.py similarity index 100% rename from aegis/aegis128x4.py rename to pyaegis/aegis128x4.py diff --git a/aegis/aegis256.py b/pyaegis/aegis256.py similarity index 100% rename from aegis/aegis256.py rename to pyaegis/aegis256.py diff --git a/aegis/aegis256x2.py b/pyaegis/aegis256x2.py similarity index 100% rename from aegis/aegis256x2.py rename to pyaegis/aegis256x2.py diff --git a/aegis/aegis256x4.py b/pyaegis/aegis256x4.py similarity index 100% rename from aegis/aegis256x4.py rename to pyaegis/aegis256x4.py diff --git a/pyproject.toml b/pyproject.toml index eacc4c4..5a54812 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["hatchling"] +requires = ["hatchling", "cffi>=2.0.0"] build-backend = "hatchling.build" [project] @@ -26,3 +26,13 @@ Homepage = "https://github.com/aegis-aead/libaegis" dev = [ "pytest>=8.4.2", ] + +[tool.hatch.build.hooks.custom] +# Placeholder build hook for compiling libaegis with Zig during wheel builds. +# The actual Zig build is intentionally not executed yet. +path = "tools/build_hook.py" + +[tool.hatch.build.targets.wheel] +# Ensure only the Python package is included by default; native artifacts +# will be added by the build hook once implemented. +packages = ["pyaegis"] diff --git a/tests/test_encrypt.py b/tests/test_encrypt.py index 3ebbda8..db25e25 100644 --- a/tests/test_encrypt.py +++ b/tests/test_encrypt.py @@ -2,13 +2,8 @@ import json from pathlib import Path from sys import stderr -import aegis.aegis128l -import aegis.aegis128x2 -import aegis.aegis128x4 -import aegis.aegis256 -import aegis.aegis256x2 -import aegis.aegis256x4 import pytest +from pyaegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4 from .util import random_split_bytes @@ -20,12 +15,12 @@ def load_encryption_test_vectors(): # Map filename to algorithm module algorithm_files = { - "aegis-128l-test-vectors.json": aegis.aegis128l, - "aegis-128x2-test-vectors.json": aegis.aegis128x2, - "aegis-128x4-test-vectors.json": aegis.aegis128x4, - "aegis-256-test-vectors.json": aegis.aegis256, - "aegis-256x2-test-vectors.json": aegis.aegis256x2, - "aegis-256x4-test-vectors.json": aegis.aegis256x4, + "aegis-128l-test-vectors.json": aegis128l, + "aegis-128x2-test-vectors.json": aegis128x2, + "aegis-128x4-test-vectors.json": aegis128x4, + "aegis-256-test-vectors.json": aegis256, + "aegis-256x2-test-vectors.json": aegis256x2, + "aegis-256x4-test-vectors.json": aegis256x4, } for filename, alg_module in algorithm_files.items(): diff --git a/tests/test_mac.py b/tests/test_mac.py index 2c956c2..03a8b81 100644 --- a/tests/test_mac.py +++ b/tests/test_mac.py @@ -1,13 +1,8 @@ import json from pathlib import Path -import aegis.aegis128l -import aegis.aegis128x2 -import aegis.aegis128x4 -import aegis.aegis256 -import aegis.aegis256x2 -import aegis.aegis256x4 import pytest +from pyaegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4 from .util import random_split_bytes @@ -24,17 +19,17 @@ def load_mac_test_vectors(): def get_algorithm_module(name): """Map test vector name to algorithm module.""" if "128L" in name: - return aegis.aegis128l + return aegis128l elif "128X2" in name: - return aegis.aegis128x2 + return aegis128x2 elif "128X4" in name: - return aegis.aegis128x4 + return aegis128x4 elif "256" in name and "256X2" not in name and "256X4" not in name: - return aegis.aegis256 + return aegis256 elif "256X2" in name: - return aegis.aegis256x2 + return aegis256x2 elif "256X4" in name: - return aegis.aegis256x4 + return aegis256x4 else: raise ValueError(f"Unknown algorithm in test vector name: {name}") diff --git a/tools/build_hook.py b/tools/build_hook.py new file mode 100644 index 0000000..452c660 --- /dev/null +++ b/tools/build_hook.py @@ -0,0 +1,70 @@ +"""Hatch build hook for building dynamic libaegis library using Zig.""" + +import shutil +import subprocess +from pathlib import Path + +from hatchling.builders.hooks.plugin.interface import BuildHookInterface + + +class BuildHook(BuildHookInterface): + """Build dynamic library with Zig and include in wheel.""" + + def initialize(self, version: str, build_data: dict) -> None: + """Build library with Zig and add it to the wheel.""" + if self.target_name != "wheel": + return + + if not shutil.which("zig"): + raise RuntimeError("Zig compiler not found in PATH") + + libaegis_dir = Path(self.root) / "libaegis" + original_build_zig = libaegis_dir / "build.zig" + if not original_build_zig.exists(): + raise RuntimeError(f"libaegis source not found at {libaegis_dir}") + + # Prepare a temporary build directory (avoid touching original files) + build_dir = Path.cwd() / "libaegis-build" + build_dir.mkdir(exist_ok=True) + build_zig = build_dir / "build.zig" + build_zig.write_text( + original_build_zig.read_text(encoding="utf-8").replace( + ".linkage = .static,", ".linkage = .dynamic," + ), + encoding="utf-8", + ) + for res in "build.zig.zon", "src": + (build_dir / res).symlink_to(libaegis_dir / res) + self.app.display_info("[aegis] Building libaegis dynamic library with Zig...") + try: + subprocess.run( + ["zig", "build", "-Drelease"], + check=True, + cwd=str(build_dir), + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + except subprocess.CalledProcessError as e: + output = e.stdout.decode(errors="replace") if e.stdout else "" + raise RuntimeError(f"Zig build failed:\n{output}") from e + + lib_dir = build_dir / "zig-out" / "lib" + + dynamic_lib = None + for lib_file in lib_dir.iterdir(): + if lib_file.name.startswith("libaegis") and lib_file.suffix in ( + ".so", + ".dylib", + ".dll", + ): + dynamic_lib = lib_file + break + + if not dynamic_lib or not dynamic_lib.exists(): + raise RuntimeError(f"Built dynamic library not found in {lib_dir}") + + if "force_include" not in build_data: + build_data["force_include"] = {} + dest_rel = str(Path("build") / dynamic_lib.name) + build_data["force_include"][str(dynamic_lib)] = dest_rel + self.app.display_info(f"[aegis] Added dynamic library to wheel: {dest_rel}") diff --git a/tools/gen_cdef.py b/tools/gen_cdef.py new file mode 100644 index 0000000..80b531f --- /dev/null +++ b/tools/gen_cdef.py @@ -0,0 +1,167 @@ +#!/usr/bin/env python3 +"""Generate CFFI cdef string from libaegis headers. + +This script parses the C header files and extracts function declarations, +typedefs, and struct definitions to generate the cdef() string needed by CFFI. +""" + +import pathlib +import re +import sys + + +def preprocess_content(content: str) -> str: + """Remove comments, preprocessor directives, and extern "C" blocks.""" + # Remove multi-line comments + content = re.sub(r"/\*.*?\*/", " ", content, flags=re.DOTALL) + # Remove line comments + content = re.sub(r"//.*$", "", content, flags=re.MULTILINE) + # Remove preprocessor directives + content = re.sub(r"^\s*#.*$", "", content, flags=re.MULTILINE) + # Remove extern "C" blocks + content = re.sub(r'extern\s+"C"\s*\{', "", content) + content = re.sub(r"(?:^|\n)\s*\}\s*(?:\n|$)", "\n", content, flags=re.MULTILINE) + + return content + + +def clean_declaration(text: str) -> str: + """Clean up a C declaration for CFFI consumption.""" + # Remove __attribute__(...) with proper nesting + while "__attribute__" in text: + old = text + text = re.sub(r"__attribute__\s*\(\([^()]*\)\)", "", text) + if text == old: + break + + # Remove CRYPTO_ALIGN(...) + text = re.sub(r"CRYPTO_ALIGN\s*\(\s*\d+\s*\)", "", text) + + # Normalize whitespace but preserve structure + lines = [] + for line in text.split("\n"): + line = re.sub(r"\s+", " ", line).strip() + if line: + lines.append(line) + + return " ".join(lines) + + +def extract_declarations(header_path: pathlib.Path) -> list[str]: + """Extract function declarations and typedefs from a header file.""" + content = header_path.read_text(encoding="utf-8") + content = preprocess_content(content) + declarations = [] + + # Extract typedefs (including structs) + typedef_pattern = r"typedef\s+struct\s+\w+\s*\{[^}]+\}\s*\w+\s*;" + for match in re.finditer(typedef_pattern, content, re.DOTALL): + decl = clean_declaration(match.group(0)) + if decl: + declarations.append(decl) + + # Extract function declarations - more permissive pattern + func_pattern = r"((?:const\s+)?(?:int|void|size_t)\s+\w+\s*\([^;]+?\)\s*;)" + for match in re.finditer(func_pattern, content, re.DOTALL): + decl = clean_declaration(match.group(0)) + if decl and "aegis" in decl.lower(): + declarations.append(decl) + + return declarations + + +def format_declaration(decl: str, max_width: int = 100) -> str: + """Format a declaration for readability, with intelligent line breaking.""" + # If it's short enough, return as-is + if len(decl) <= max_width: + return decl + + # For function declarations, try to break at parameter boundaries + if "(" in decl and ")" in decl: + # Find the function name and opening paren + match = re.match(r"(.*?\s+\w+\s*)\((.*)\)(.*)", decl) + if match: + prefix, params, suffix = match.groups() + # Break parameters if they're too long + if len(prefix) + len(params) + 2 > max_width: + # Split parameters + param_list = [p.strip() for p in params.split(",")] + if len(param_list) > 1: + formatted_params = (",\n" + " " * (len(prefix) + 1)).join( + param_list + ) + return f"{prefix}({formatted_params}){suffix}" + + return decl + + +def generate_cdef(include_dir: pathlib.Path) -> str: + """Generate the complete CFFI cdef string from all aegis headers.""" + + lines = [ + "typedef unsigned char uint8_t;", + "typedef unsigned long size_t;", + "", + ] + + # Header files in order, skipping aegis.h as it might be included elsewhere + headers = [ + "aegis.h", + "aegis128l.h", + "aegis128x2.h", + "aegis128x4.h", + "aegis256.h", + "aegis256x2.h", + "aegis256x4.h", + ] + + for header_name in headers: + header_path = include_dir / header_name + if not header_path.exists(): + print(f"Warning: {header_name} not found", file=sys.stderr) + continue + + lines.append(f"/* {header_name} */") + declarations = extract_declarations(header_path) + + for decl in declarations: + formatted = format_declaration(decl) + lines.append(formatted) + + lines.append("") + + # Add libc bits for aligned allocation + lines.extend( + [ + "/* libc bits for aligned allocation on POSIX */", + "int posix_memalign(void **memptr, size_t alignment, size_t size);", + "void free(void *ptr);", + ] + ) + + return "\n".join(lines) + + +def main() -> int: + # Find the include directory + root = pathlib.Path(__file__).resolve().parents[2] + include_dir = root / "src" / "include" + + if not include_dir.exists(): + print(f"Include directory not found: {include_dir}", file=sys.stderr) + return 1 + + cdef_string = generate_cdef(include_dir) + + # Write to a file in the pyaegis/build subdirectory + output_dir = root / "python" / "pyaegis" / "build" + output_dir.mkdir(exist_ok=True) + output_path = output_dir / "aegis_cdef.h" + output_path.write_text(cdef_string, encoding="utf-8") + print(f"Generated: {output_path}", file=sys.stderr) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())