Replaces the removed normalize_line_endings rewriter: release.py now
fails listing any text file containing CRLF instead of silently
rewriting it. .gitattributes (eol=lf) remains the preventive half.
release.py previously ran the code generator, 'ruff check --fix',
'ruff format', and line-ending normalization before building. Any drift
rewrote tracked files, dirtying the checkout and turning the release
into a dev version (the v0.10.6 PyPI 400). Generation and linting are
now validation-only:
- tools/generate.py gains a --check mode that reports stale generated
files without writing (comparing Python content in ruff-formatted
form, matching what write + format would produce)
- release.py runs generate --check, ruff check (no --fix), and
ruff format --check, failing with remediation hints
- normalize_line_endings() is removed; .gitattributes keeps checkouts LF
The tagged v0.10.6 CI build produced wheels versioned
0.10.7.dev0+g503ce8e49.d20261004: setup.py was not ruff-format-clean, so
release.py's own 'ruff format .' step rewrote it, dirtying the checkout
before setuptools_scm ran. PyPI then rejected the local version segment
with HTTP 400.
- ruff-format setup.py
- release.py: on a v*-tagged HEAD, fail before building if the version
is not a clean release, printing the dirty files
- add .gitattributes (eol=lf) so Windows checkouts can't dirty the tree
via normalize_line_endings either
os.open() on Windows defaults to O_TEXT, so the CRT translated \r\n in
binary ciphertext: writes grew, offsets shifted, reads came up short and
the RAF callbacks failed with EIO (probe/read/write depending on where
a 0x0A byte landed). Pass O_BINARY where it exists.
Also reconfigure stdout/stderr to UTF-8 in tools/release.py so the
✓/✗/⚠ summary lines don't crash on cp1252 Windows consoles.
The generated cdef typedef'd size_t as unsigned long: 8 bytes on LP64
but only 4 on 64-bit Windows, where C's size_t is 8. cffi's import-time
struct layout verification then failed (aegis_raf_scratch.len: cdef
says 4, C compiler says 8). cffi pre-defines size_t/uint8_t with the
correct platform sizes, so don't typedef them.
Also hard-fail 32-bit build attempts in setup.py; 32-bit platforms are
no longer supported.
Windows runners run as SYSTEM with the workspace under
C:\Windows\System32\config\systemprofile. cl.exe is 32-bit, so WOW64
file redirection sends its System32 access to SysWOW64 and it cannot
see the checked-out sources (C1083: aegis.h not found), while 64-bit
processes (git, python) see them fine. Move the Windows checkout/build
to C:\aeg-build.
- Linux runner lacks patchelf; add the patchelf PyPI package (linux
only) to dev deps so uv run puts it on PATH for auditwheel repair.
- setup.py anchored all libaegis paths to __file__: newer setuptools
invoke the compiler from a temp build dir, breaking CWD-relative
-Ilibaegis/src/include on Windows (C1083 aegis.h not found).
Fixes CI EXE001 (shebang on non-executable file). Running the script
directly puts src/aeg on sys.path, where aeg/random.py would shadow the
stdlib random module that secrets and others import — pop it before any
imports so both direct execution and -m aeg.benchmark work.
Following upstream libaegis 0.10.5 (874f884): the absorbing-mode
unauthenticated pair is deprecated in favor of the pure keystream XOR.
- stream_xor(key, nonce, data, into=None): same function encrypts and
decrypts; output equals data XOR stream(key, nonce); supports in-place
use via into (same buffer semantics as elsewhere).
- encrypt_unauthenticated/decrypt_unauthenticated kept for the old
format but now emit DeprecationWarning; removed from README (stream_xor
documented under Keystream generation instead).
Each cipher module's random() now returns a thread-local Random that is
created on first use and reused thereafter. Each thread gets its own
OS-seeded generator (own key+nonce), so no locking and no cross-thread
nonce collisions. random_key() and random_nonce() draw from this CSPRNG
instead of secrets.token_bytes, making them as fast as the stream.
Changes made in the aegis256x4.py template and regenerated.
StreamStorage wraps any seek-based file-like object using the same
lock + seek/read/write pattern as FileStorage's fallback for platforms
without pread/pwrite. create()/open() auto-wrap io.BytesIO, so in-memory
use no longer requires the explicit BytesIOStorage wrapper. The caller
keeps ownership of the stream (Raf does not close it).
mmap-backed buffer locked against swapping (mlock/VirtualLock) and
excluded from core dumps (MADV_DONTDUMP) where available, wiped on
close. Not used for return values; intended as an 'into' target (and
key input) to keep sensitive material out of the regular heap.
Adapted from quantumpipe's securebuf module.
- requires-python >= 3.12; abi3 limited API tag cp310 -> cp312
- util.Buffer now imported directly from collections.abc (no fallback)
- release.py: ABI3 wheel covers 3.12-3.15; PyPy wheels dropped (PyPy
has no 3.12 support yet)
Running generate.py standalone previously rewrote _ciphers.py with
formatting that differed from the committed (ruff-formatted) style,
creating spurious diffs. release.py already ran ruff after generation;
now generate.py does too, so both entry points leave the tree in the
same state.
Implement Python bindings for the libaegis 0.10.6 RAF API:
- Thin per-variant wrappers (raf_create/open/read/write/truncate/size/
sync/close, merkle_rebuild/verify/commitment) generated from a template
in tools/generate.py, with errno-mapped exceptions.
- aeg.raf: high-level file-like API (Raf, create/open/probe,
derive_master_key) with pluggable Storage backends (FileStorage,
BytesIOStorage) and Merkle tree integrity via a MerkleHasher protocol
(SHA256MerkleHasher default), adapted from pyaegis (MIT).
- aeg.random: CSPRNG facility. Random produces AEGIS keystream seeded
once from the OS, exposed per cipher module as cipher.random() and
usable as the RAF RNG (as_raf_rng) or standalone RandomSource.
- Tests: roundtrip/random-access/seek/truncate/Merkle coverage for all
six variants, pyaegis-style API checks, and a Merkle fuzz test.
Matrix job builds and tests wheels on linux/macos/windows (no Gitea
release, unlike mediahive); publish job uploads the merged dist/ to PyPI
only after all platforms succeed. release.py gains --no-upload for the
matrix jobs.
- Mac class follows hashlib API: digest functions added and finalization no longer modifies state.
- Encryptor and Decryptor now raise RuntimeError if still used after final.
Documentation updated with the changes and further examples.
Tests updated with the changes, new test module for error cases (test_raises).
Docstrings improved.