Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88efc4cabc | ||
|
|
04b11e9925 | ||
|
|
d8a9a7ee9d | ||
|
|
f5430a6ad4 | ||
|
|
f84ef727d3 | ||
|
|
bb9d11842a | ||
|
|
20e0ed8c5f | ||
|
|
62fc8fa855 | ||
|
|
67c2958384 | ||
|
|
a6faaf9f62 | ||
|
|
75cbc76845 | ||
|
|
95563a43d1 | ||
|
|
e58990a1c2 | ||
|
|
13445887e9 | ||
|
|
751a929836 | ||
|
|
d4f8be69ed | ||
|
|
77601d7f57 | ||
|
|
4356e57ace | ||
|
|
5e19bd980e | ||
|
|
63ccef577d | ||
|
|
555bbcf2a5 | ||
|
|
1dcdafa008 | ||
|
|
96ce7867de | ||
|
|
b15174af8b | ||
|
|
02eb4d7718 | ||
|
|
1b4d43a448 | ||
|
|
a8947c23b3 | ||
|
|
2677df3bde | ||
|
|
7175654b27 | ||
|
|
46dff56e28 | ||
|
|
fc76bc4280 | ||
|
|
9f2b931a0b | ||
|
|
fbf9c944e6 | ||
|
|
f8cc02eb41 | ||
|
|
fd24bb02f8 | ||
|
|
285f11299e | ||
|
|
3248fccbac | ||
|
|
17a5f45394 | ||
|
|
d43a22bc6c | ||
|
|
c8fe16d21f | ||
|
|
ea066e101d | ||
|
|
42ddaac6bc | ||
|
|
8898cec50f | ||
|
|
3579b94e83 | ||
|
|
bcf4655f64 | ||
|
|
438627e0db | ||
|
|
02310675b7 | ||
|
|
6ceb2971fa | ||
|
|
7541d9d837 |
+3
-3
@@ -3,9 +3,9 @@
|
|||||||
*.egg-info
|
*.egg-info
|
||||||
/dist
|
/dist
|
||||||
/build
|
/build
|
||||||
/src/aeg/build
|
/pyaegis/build
|
||||||
/src/aeg/_aegis*.so
|
/pyaegis/_aegis.*.so
|
||||||
/src/aeg/_aegis*.pyd
|
/pyaegis/_aegis.*.pyd
|
||||||
__pycache__
|
__pycache__
|
||||||
!.gitignore
|
!.gitignore
|
||||||
!.gitmodules
|
!.gitmodules
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Building aeg
|
# Building pyaegis
|
||||||
|
|
||||||
This document contains instructions for developers who want to build aeg from source.
|
This document contains instructions for developers who want to build pyaegis from source.
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ This document contains instructions for developers who want to build aeg from so
|
|||||||
|
|
||||||
### Installing Zig
|
### Installing Zig
|
||||||
|
|
||||||
aeg uses Zig to build the underlying libaegis C library. Install Zig from [ziglang.org/download](https://ziglang.org/download/) or using your package manager:
|
pyaegis uses Zig to build the underlying libaegis C library. Install Zig from [ziglang.org/download](https://ziglang.org/download/) or using your package manager:
|
||||||
|
|
||||||
- **macOS**: `brew install zig`
|
- **macOS**: `brew install zig`
|
||||||
- **Linux**: See [Zig installation guide](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
|
- **Linux**: See [Zig installation guide](https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager)
|
||||||
@@ -22,8 +22,8 @@ aeg uses Zig to build the underlying libaegis C library. Install Zig from [zigla
|
|||||||
Clone the repository with submodules:
|
Clone the repository with submodules:
|
||||||
|
|
||||||
```fish
|
```fish
|
||||||
git clone --recursive https://github.com/LeoVasanko/aeg.git
|
git clone --recursive https://github.com/LeoVasanko/pyaegis.git
|
||||||
cd aeg
|
cd pyaegis
|
||||||
```
|
```
|
||||||
|
|
||||||
If you already cloned without `--recursive`, initialize submodules:
|
If you already cloned without `--recursive`, initialize submodules:
|
||||||
@@ -74,7 +74,7 @@ This creates files in the `dist/` directory.
|
|||||||
|
|
||||||
## Code Generation
|
## Code Generation
|
||||||
|
|
||||||
The Python modules and CFFI definitions are generated from C sources and templates. If you modify the core implementation in `src/aeg/aegis256x4.py` or update libaegis headers, regenerate all files:
|
The Python modules and CFFI definitions are generated from C sources and templates. If you modify the core implementation in `pyaegis/aegis256x4.py` or update libaegis headers, regenerate all files:
|
||||||
|
|
||||||
```fish
|
```fish
|
||||||
python tools/generate.py
|
python tools/generate.py
|
||||||
@@ -100,7 +100,7 @@ If you cannot install Zig, you may manually compile in the libaegis folder (Zig,
|
|||||||
|
|
||||||
## Project Structure
|
## Project Structure
|
||||||
|
|
||||||
- `src/aeg/` - Python package source
|
- `pyaegis/` - Python package source
|
||||||
- `libaegis/` - C library source (submodule)
|
- `libaegis/` - C library source (submodule)
|
||||||
- `tests/` - Test suite
|
- `tests/` - Test suite
|
||||||
- `tools/` - Code generation scripts and `build_backend.py` used to build libaegis
|
- `tools/` - Code generation scripts and `build_backend.py` used to build libaegis
|
||||||
|
|||||||
+3
-1
@@ -1,11 +1,13 @@
|
|||||||
include src/aeg/aegis_cdef.h
|
include pyaegis/aegis_cdef.h
|
||||||
include setup.py
|
include setup.py
|
||||||
include tools/build_backend.py
|
include tools/build_backend.py
|
||||||
include BUILD.md
|
include BUILD.md
|
||||||
include README.md
|
include README.md
|
||||||
recursive-include libaegis *.c *.h *.zig *.zon
|
recursive-include libaegis *.c *.h *.zig *.zon
|
||||||
|
include libaegis/CMakeLists.txt
|
||||||
include libaegis/LICENSE
|
include libaegis/LICENSE
|
||||||
include libaegis/README.md
|
include libaegis/README.md
|
||||||
|
recursive-include libaegis/cmake *.cmake *.cmake.in
|
||||||
graft libaegis/src
|
graft libaegis/src
|
||||||
include libaegis/build.zig
|
include libaegis/build.zig
|
||||||
include libaegis/build.zig.zon
|
include libaegis/build.zig.zon
|
||||||
|
|||||||
@@ -1,28 +1,33 @@
|
|||||||
# AEGIS Cipher Python Binding
|
# pyaegis
|
||||||
|
|
||||||
[](https://badge.fury.io/py/aeg)
|
[](https://badge.fury.io/py/pyaegis)
|
||||||
|
|
||||||
Safe Python bindings for the AEGIS family of very fast authenticated encryption algorithms via libaegis. The module runs without compilation required on Windows, Mac and Linux (has precompiled wheels). For other platforms compilation is performed at install time.
|
Safe Python bindings for the AEGIS family of very fast authenticated encryption algorithms (via libaegis).
|
||||||
|
|
||||||
AEGIS enables extremely fast Encryption, MAC and CSPRNG - many times faster than AES, ChaCha20 or traditional random number generators. Authenticated Encryption with Additional Data is supported with the MAC derived from the cipher state at the end, making it different from other AEADs like AES-GCM and ChaCha20-Poly1305. The whole internal state thus depends on the prior data, and it is neither Encrypt-Then-Mac nor Mac-The-Encrypt scheme when both features are used together.
|
|
||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
```sh
|
Using [uv](https://docs.astral.sh/uv/getting-started/installation/):
|
||||||
pip install aeg
|
```fish
|
||||||
|
uv pip install git+https://github.com/LeoVasanko/pyaegis.git
|
||||||
```
|
```
|
||||||
|
|
||||||
Or add to your project using [UV](https://docs.astral.sh/uv/getting-started/installation/):
|
For development builds, see BUILD.md.
|
||||||
```sh
|
|
||||||
uv add aeg
|
## Variants
|
||||||
```
|
|
||||||
|
All submodules expose the same API; pick one for your key/nonce size and platform:
|
||||||
|
|
||||||
|
- aegis128l (16-byte key, 16-byte nonce)
|
||||||
|
- aegis256 (32-byte key, 32-byte nonce)
|
||||||
|
- aegis128x2 / aegis128x4 (multi-lane 128-bit; best throughput on SIMD-capable CPUs)
|
||||||
|
- aegis256x2 / aegis256x4 (multi-lane 256-bit)
|
||||||
|
|
||||||
## Quick start
|
## Quick start
|
||||||
|
|
||||||
Normal authenticated encryption using the AEGIS-128X4 algorithm:
|
Normal authenticated encryption using the AEGIS-128X4 algorithm:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from aeg import aegis128x4 as ciph
|
from pyaegis import aegis128x4 as ciph
|
||||||
|
|
||||||
key = ciph.random_key() # Secret key (stored securely)
|
key = ciph.random_key() # Secret key (stored securely)
|
||||||
nonce = ciph.random_nonce() # Public nonce (recreated for each message)
|
nonce = ciph.random_nonce() # Public nonce (recreated for each message)
|
||||||
@@ -33,27 +38,6 @@ pt = ciph.decrypt(key, nonce, ct) # Raises ValueError if anything was tampered
|
|||||||
assert pt == msg
|
assert pt == msg
|
||||||
```
|
```
|
||||||
|
|
||||||
## Variants
|
|
||||||
|
|
||||||
All submodules expose the same API; pick one for your needs. The 256 bit variants offer maximal security and use larger key and nonce, while the 128 bit variants run slightly faster and use smaller key and nonce while still providing strong security. The MAC length does not depend on the variant. Note that the x2 and x4 variants are typically the fastest (depending on CPU) by utilizing SIMD multi-lane processing for the highest throughput.
|
|
||||||
|
|
||||||
| Variant | Key/Nonce Bytes | Notes |
|
|
||||||
|----------------|----------------:|-------------------------|
|
|
||||||
| **aegis128l** | 16 | |
|
|
||||||
| **aegis128x2** | 16 | Fastest on Intel Core |
|
|
||||||
| **aegis128x4** | 16 | Fastest on AMD and Xeon |
|
|
||||||
| **aegis256** | 32 | |
|
|
||||||
| **aegis256x2** | 32 | Fast on Intel Core |
|
|
||||||
| **aegis256x4** | 32 | Fast on AMD and Xeon |
|
|
||||||
|
|
||||||
Instead of importing the submodules, you can obtain one by its name string:
|
|
||||||
|
|
||||||
```python
|
|
||||||
import aeg
|
|
||||||
|
|
||||||
ciph = aeg.cipher("AEGIS-128X2") # Also accepts "aegis128x2" and other forms
|
|
||||||
```
|
|
||||||
|
|
||||||
## API overview
|
## API overview
|
||||||
|
|
||||||
Common parameters and returns (applies to all items below):
|
Common parameters and returns (applies to all items below):
|
||||||
@@ -99,13 +83,13 @@ The object releases its state and becomes unusable after final has been called.
|
|||||||
|
|
||||||
No encryption, but prevents changes to the data without the correct key.
|
No encryption, but prevents changes to the data without the correct key.
|
||||||
|
|
||||||
- mac(key, nonce, data, maclen=16, into=None) -> mac bytes
|
- mac(key, nonce, data, maclen=16, into=None) -> mac
|
||||||
- Mac(key, nonce, maclen=16)
|
- Mac(key, nonce, maclen=16)
|
||||||
- update(data)
|
- update(data)
|
||||||
- final([into]) -> mac bytes
|
- final([into]) -> mac
|
||||||
- verify(mac) -> raises ValueError on failure
|
- verify(mac) -> raises ValueError on failure
|
||||||
- digest() -> mac bytes
|
- digest() -> bytes
|
||||||
- hexdigest() -> mac str
|
- hexdigest() -> str
|
||||||
- reset()
|
- reset()
|
||||||
- clone() -> Mac
|
- clone() -> Mac
|
||||||
|
|
||||||
@@ -139,7 +123,7 @@ Constants (per module): NAME, KEYBYTES, NONCEBYTES, MACBYTES, MACBYTES_LONG, RAT
|
|||||||
|
|
||||||
A cryptographically secure keyed hash is produced. The example uses all zeroes for the nonce to always produce the same hash for the same key:
|
A cryptographically secure keyed hash is produced. The example uses all zeroes for the nonce to always produce the same hash for the same key:
|
||||||
```python
|
```python
|
||||||
from aeg import aegis256x4 as ciph
|
from pyaegis import aegis256x4 as ciph
|
||||||
key, nonce = ciph.random_key(), bytes(ciph.NONCEBYTES)
|
key, nonce = ciph.random_key(), bytes(ciph.NONCEBYTES)
|
||||||
|
|
||||||
mac = ciph.mac(key, nonce, b"message", maclen=32)
|
mac = ciph.mac(key, nonce, b"message", maclen=32)
|
||||||
@@ -162,7 +146,7 @@ b.verify(mac) # Raises ValueError
|
|||||||
Keeping the ciphertext, mac and ad separate. The ad represents a file header that needs to be tamper proofed.
|
Keeping the ciphertext, mac and ad separate. The ad represents a file header that needs to be tamper proofed.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from aeg import aegis256x4 as ciph
|
from pyaegis import aegis256x4 as ciph
|
||||||
key, nonce = ciph.random_key(), ciph.random_nonce()
|
key, nonce = ciph.random_key(), ciph.random_nonce()
|
||||||
|
|
||||||
ct, mac = ciph.encrypt_detached(key, nonce, b"secret", ad=b"header")
|
ct, mac = ciph.encrypt_detached(key, nonce, b"secret", ad=b"header")
|
||||||
@@ -178,7 +162,7 @@ ciph.wipe(pt)
|
|||||||
Class-based interface for incremental updates is an alternative to the one-shot functions. Not to be confused with separately verified ciphertext frames (see the next example).
|
Class-based interface for incremental updates is an alternative to the one-shot functions. Not to be confused with separately verified ciphertext frames (see the next example).
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from aeg import aegis256x4 as ciph
|
from pyaegis import aegis256x4 as ciph
|
||||||
key, nonce = ciph.random_key(), ciph.random_nonce()
|
key, nonce = ciph.random_key(), ciph.random_nonce()
|
||||||
|
|
||||||
enc = ciph.Encryptor(key, nonce, ad=b"header", maclen=16)
|
enc = ciph.Encryptor(key, nonce, ad=b"header", maclen=16)
|
||||||
@@ -198,7 +182,7 @@ It is often practical to split larger messages into frames that can be individua
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
# Encryption settings
|
# Encryption settings
|
||||||
from aeg import aegis128x4 as ciph
|
from pyaegis import aegis128x4 as ciph
|
||||||
key = b"sixteenbyte key!" # 16 bytes secret key for aegis128* algorithms
|
key = b"sixteenbyte key!" # 16 bytes secret key for aegis128* algorithms
|
||||||
framebytes = 80 # In real applications 1 MiB or more is practical
|
framebytes = 80 # In real applications 1 MiB or more is practical
|
||||||
maclen = ciph.MACBYTES # 16
|
maclen = ciph.MACBYTES # 16
|
||||||
@@ -218,7 +202,7 @@ with open("encrypted.bin", "wb") as f:
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
# Decryption needs same values as encryption
|
# Decryption needs same values as encryption
|
||||||
from aeg import aegis128x4 as ciph
|
from pyaegis import aegis128x4 as ciph
|
||||||
key = b"sixteenbyte key!"
|
key = b"sixteenbyte key!"
|
||||||
framebytes = 80
|
framebytes = 80
|
||||||
maclen = ciph.MACBYTES
|
maclen = ciph.MACBYTES
|
||||||
@@ -239,7 +223,7 @@ with open("encrypted.bin", "rb") as f:
|
|||||||
The stream generator is much faster than any traditional random number generator, cryptographically secure and seekable. Use `random_key()` for unpredictable output.
|
The stream generator is much faster than any traditional random number generator, cryptographically secure and seekable. Use `random_key()` for unpredictable output.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from aeg import aegis128x4 as ciph
|
from pyaegis import aegis128x4 as ciph
|
||||||
|
|
||||||
key = b"SeedForReplay001" # A non-random deterministic seed (16 bytes)
|
key = b"SeedForReplay001" # A non-random deterministic seed (16 bytes)
|
||||||
nonce = bytearray(ciph.NONCEBYTES) # All-zeroes nonce
|
nonce = bytearray(ciph.NONCEBYTES) # All-zeroes nonce
|
||||||
@@ -263,7 +247,7 @@ Foreign arrays can be used. This example fills a Numpy array with random integer
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from aeg import aegis128x4 as ciph
|
from pyaegis import aegis128x4 as ciph
|
||||||
key, nonce = ciph.random_key(), ciph.random_nonce()
|
key, nonce = ciph.random_key(), ciph.random_nonce()
|
||||||
|
|
||||||
arr = np.empty(10, dtype=np.uint64) # Uninitialised integer array
|
arr = np.empty(10, dtype=np.uint64) # Uninitialised integer array
|
||||||
@@ -274,7 +258,7 @@ print(arr)
|
|||||||
In-place operations are supported when the input and the output point to the same location in memory. When using attached MAC tag, the input buffer needs to be sliced to correct length:
|
In-place operations are supported when the input and the output point to the same location in memory. When using attached MAC tag, the input buffer needs to be sliced to correct length:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from aeg import aegis256x4 as ciph
|
from pyaegis import aegis256x4 as ciph
|
||||||
key, nonce = ciph.random_key(), ciph.random_nonce()
|
key, nonce = ciph.random_key(), ciph.random_nonce()
|
||||||
buf = memoryview(bytearray(1000)) # memoryview[:len] is still in the same buffer (no copy)
|
buf = memoryview(bytearray(1000)) # memoryview[:len] is still in the same buffer (no copy)
|
||||||
buf[:7] = b"message"
|
buf[:7] = b"message"
|
||||||
@@ -292,10 +276,10 @@ Detached and unauthenticated modes can use same size input and output (no MAC ad
|
|||||||
|
|
||||||
Runtime CPU feature detection selects optimized code paths (AES-NI, ARM Crypto, AVX2/AVX-512). Multi-lane variants (x2/x4) offer higher throughput on suitable CPUs.
|
Runtime CPU feature detection selects optimized code paths (AES-NI, ARM Crypto, AVX2/AVX-512). Multi-lane variants (x2/x4) offer higher throughput on suitable CPUs.
|
||||||
|
|
||||||
Benchmarks using the included benchmark module, run on Intel i7-14700, linux, single core (the software is not multithreaded). Note that the results are in megabits per second, not bytes. The CPU lacks AVX-512 that makes the X4 variants faster on processors supporting it (most AMD, Xeon).
|
Benchmarks using the included benchmark module, run on Intel i7-14700, linux, single core (the software is not multithreaded). Note that the results are in megabits per second, not bytes. The CPU lacks AVX-512 that makes the X4 variants faster on AMD hardware.
|
||||||
|
|
||||||
```sh
|
```fish
|
||||||
uv run -m aeg.benchmark
|
$ uv run -m pyaegis.benchmark
|
||||||
AEGIS-256 103166.24 Mb/s
|
AEGIS-256 103166.24 Mb/s
|
||||||
AEGIS-256X2 184225.50 Mb/s
|
AEGIS-256X2 184225.50 Mb/s
|
||||||
AEGIS-256X4 194018.26 Mb/s
|
AEGIS-256X4 194018.26 Mb/s
|
||||||
@@ -311,8 +295,8 @@ AEGIS-256X4 MAC 315919.87 Mb/s
|
|||||||
```
|
```
|
||||||
|
|
||||||
The Python library performance is similar to that of the C library:
|
The Python library performance is similar to that of the C library:
|
||||||
```sh
|
```fish
|
||||||
./libaegis/zig-out/bin/benchmark
|
$ ./libaegis/zig-out/bin/benchmark
|
||||||
AEGIS-256 107820.86 Mb/s
|
AEGIS-256 107820.86 Mb/s
|
||||||
AEGIS-256X2 205025.57 Mb/s
|
AEGIS-256X2 205025.57 Mb/s
|
||||||
AEGIS-256X4 223361.81 Mb/s
|
AEGIS-256X4 223361.81 Mb/s
|
||||||
@@ -326,7 +310,3 @@ AEGIS-256 MAC 116776.62 Mb/s
|
|||||||
AEGIS-256X2 MAC 224150.04 Mb/s
|
AEGIS-256X2 MAC 224150.04 Mb/s
|
||||||
AEGIS-256X4 MAC 392088.05 Mb/s
|
AEGIS-256X4 MAC 392088.05 Mb/s
|
||||||
```
|
```
|
||||||
|
|
||||||
## Alternatives
|
|
||||||
|
|
||||||
There is also a package named [pyaegis](https://github.com/jedisct1/pyaegis) on PyPI that is unrelated to this module, but that also binds to the libaegis C library. There are also a number of modules named aegis from different packages not at all related to the encryption algorithm.
|
|
||||||
|
|||||||
+1
-1
Submodule libaegis updated: 7b667dd883...4a234009c9
@@ -1,6 +1,6 @@
|
|||||||
"""Loader for libaegis CFFI extension module."""
|
"""Loader for libaegis CFFI extension module."""
|
||||||
|
|
||||||
from aeg._aegis import ffi, lib
|
from pyaegis._aegis import ffi, lib
|
||||||
|
|
||||||
__all__ = ["ffi", "lib"]
|
__all__ = ["ffi", "lib"]
|
||||||
|
|
||||||
@@ -295,7 +295,9 @@ def decrypt(
|
|||||||
out = bytearray(expected_out)
|
out = bytearray(expected_out)
|
||||||
else:
|
else:
|
||||||
if into.nbytes < expected_out:
|
if into.nbytes < expected_out:
|
||||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
raise TypeError(
|
||||||
|
"into length must be at least ct.nbytes - maclen"
|
||||||
|
)
|
||||||
out = into
|
out = into
|
||||||
|
|
||||||
rc = _lib.aegis128l_decrypt(
|
rc = _lib.aegis128l_decrypt(
|
||||||
@@ -580,9 +582,7 @@ class Mac:
|
|||||||
out = into
|
out = into
|
||||||
|
|
||||||
clone = self.clone()
|
clone = self.clone()
|
||||||
rc = _lib.aegis128l_mac_final(
|
rc = _lib.aegis128l_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
|
||||||
)
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
@@ -708,17 +708,24 @@ class Encryptor:
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
"into length must be >= expected output size for this update"
|
"into length must be >= expected output size for this update"
|
||||||
)
|
)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis128l_state_encrypt_update(
|
rc = _lib.aegis128l_state_encrypt_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(message),
|
_ptr(message),
|
||||||
message.nbytes,
|
message.nbytes,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt update failed: {err_name}")
|
raise RuntimeError(
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||||
|
)
|
||||||
|
w = int(written[0])
|
||||||
|
assert w == expected_out
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||||
"""Finalize encryption and return the authentication tag.
|
"""Finalize encryption and return the authentication tag.
|
||||||
@@ -739,17 +746,24 @@ class Encryptor:
|
|||||||
if into is not None:
|
if into is not None:
|
||||||
into = memoryview(into)
|
into = memoryview(into)
|
||||||
out = into if into is not None else bytearray(maclen)
|
out = into if into is not None else bytearray(maclen)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis128l_state_encrypt_final(
|
rc = _lib.aegis128l_state_encrypt_final(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out),
|
ffi.from_buffer(out),
|
||||||
|
memoryview(out).nbytes,
|
||||||
|
written,
|
||||||
maclen,
|
maclen,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||||
|
w = int(written[0])
|
||||||
|
if into is None:
|
||||||
|
# Only the tag bytes are returned when we allocate the buffer
|
||||||
|
assert w == maclen
|
||||||
self._state = None
|
self._state = None
|
||||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Decryptor:
|
class Decryptor:
|
||||||
@@ -823,9 +837,12 @@ class Decryptor:
|
|||||||
out_mv = memoryview(out)
|
out_mv = memoryview(out)
|
||||||
if out_mv.nbytes < expected_out:
|
if out_mv.nbytes < expected_out:
|
||||||
raise TypeError("into length must be >= required capacity for this update")
|
raise TypeError("into length must be >= required capacity for this update")
|
||||||
rc = _lib.aegis128l_state_decrypt_update(
|
written = ffi.new("size_t *")
|
||||||
|
rc = _lib.aegis128l_state_decrypt_detached_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(ct),
|
_ptr(ct),
|
||||||
ct.nbytes,
|
ct.nbytes,
|
||||||
)
|
)
|
||||||
@@ -833,7 +850,9 @@ class Decryptor:
|
|||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
w = int(written[0])
|
||||||
|
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, mac: Buffer) -> None:
|
def final(self, mac: Buffer) -> None:
|
||||||
"""Finalize decryption by verifying the MAC tag.
|
"""Finalize decryption by verifying the MAC tag.
|
||||||
@@ -852,7 +871,9 @@ class Decryptor:
|
|||||||
mac = memoryview(mac)
|
mac = memoryview(mac)
|
||||||
if mac.nbytes != maclen:
|
if mac.nbytes != maclen:
|
||||||
raise TypeError(f"mac length must be {maclen}")
|
raise TypeError(f"mac length must be {maclen}")
|
||||||
rc = _lib.aegis128l_state_decrypt_final(self._state.ptr, _ptr(mac), maclen)
|
rc = _lib.aegis128l_state_decrypt_detached_final(
|
||||||
|
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||||
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise ValueError("authentication failed")
|
raise ValueError("authentication failed")
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -295,7 +295,9 @@ def decrypt(
|
|||||||
out = bytearray(expected_out)
|
out = bytearray(expected_out)
|
||||||
else:
|
else:
|
||||||
if into.nbytes < expected_out:
|
if into.nbytes < expected_out:
|
||||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
raise TypeError(
|
||||||
|
"into length must be at least ct.nbytes - maclen"
|
||||||
|
)
|
||||||
out = into
|
out = into
|
||||||
|
|
||||||
rc = _lib.aegis128x2_decrypt(
|
rc = _lib.aegis128x2_decrypt(
|
||||||
@@ -580,9 +582,7 @@ class Mac:
|
|||||||
out = into
|
out = into
|
||||||
|
|
||||||
clone = self.clone()
|
clone = self.clone()
|
||||||
rc = _lib.aegis128x2_mac_final(
|
rc = _lib.aegis128x2_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
|
||||||
)
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
@@ -708,17 +708,24 @@ class Encryptor:
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
"into length must be >= expected output size for this update"
|
"into length must be >= expected output size for this update"
|
||||||
)
|
)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis128x2_state_encrypt_update(
|
rc = _lib.aegis128x2_state_encrypt_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(message),
|
_ptr(message),
|
||||||
message.nbytes,
|
message.nbytes,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt update failed: {err_name}")
|
raise RuntimeError(
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||||
|
)
|
||||||
|
w = int(written[0])
|
||||||
|
assert w == expected_out
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||||
"""Finalize encryption and return the authentication tag.
|
"""Finalize encryption and return the authentication tag.
|
||||||
@@ -739,17 +746,24 @@ class Encryptor:
|
|||||||
if into is not None:
|
if into is not None:
|
||||||
into = memoryview(into)
|
into = memoryview(into)
|
||||||
out = into if into is not None else bytearray(maclen)
|
out = into if into is not None else bytearray(maclen)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis128x2_state_encrypt_final(
|
rc = _lib.aegis128x2_state_encrypt_final(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out),
|
ffi.from_buffer(out),
|
||||||
|
memoryview(out).nbytes,
|
||||||
|
written,
|
||||||
maclen,
|
maclen,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||||
|
w = int(written[0])
|
||||||
|
if into is None:
|
||||||
|
# Only the tag bytes are returned when we allocate the buffer
|
||||||
|
assert w == maclen
|
||||||
self._state = None
|
self._state = None
|
||||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Decryptor:
|
class Decryptor:
|
||||||
@@ -823,9 +837,12 @@ class Decryptor:
|
|||||||
out_mv = memoryview(out)
|
out_mv = memoryview(out)
|
||||||
if out_mv.nbytes < expected_out:
|
if out_mv.nbytes < expected_out:
|
||||||
raise TypeError("into length must be >= required capacity for this update")
|
raise TypeError("into length must be >= required capacity for this update")
|
||||||
rc = _lib.aegis128x2_state_decrypt_update(
|
written = ffi.new("size_t *")
|
||||||
|
rc = _lib.aegis128x2_state_decrypt_detached_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(ct),
|
_ptr(ct),
|
||||||
ct.nbytes,
|
ct.nbytes,
|
||||||
)
|
)
|
||||||
@@ -833,7 +850,9 @@ class Decryptor:
|
|||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
w = int(written[0])
|
||||||
|
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, mac: Buffer) -> None:
|
def final(self, mac: Buffer) -> None:
|
||||||
"""Finalize decryption by verifying the MAC tag.
|
"""Finalize decryption by verifying the MAC tag.
|
||||||
@@ -852,7 +871,9 @@ class Decryptor:
|
|||||||
mac = memoryview(mac)
|
mac = memoryview(mac)
|
||||||
if mac.nbytes != maclen:
|
if mac.nbytes != maclen:
|
||||||
raise TypeError(f"mac length must be {maclen}")
|
raise TypeError(f"mac length must be {maclen}")
|
||||||
rc = _lib.aegis128x2_state_decrypt_final(self._state.ptr, _ptr(mac), maclen)
|
rc = _lib.aegis128x2_state_decrypt_detached_final(
|
||||||
|
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||||
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise ValueError("authentication failed")
|
raise ValueError("authentication failed")
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -295,7 +295,9 @@ def decrypt(
|
|||||||
out = bytearray(expected_out)
|
out = bytearray(expected_out)
|
||||||
else:
|
else:
|
||||||
if into.nbytes < expected_out:
|
if into.nbytes < expected_out:
|
||||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
raise TypeError(
|
||||||
|
"into length must be at least ct.nbytes - maclen"
|
||||||
|
)
|
||||||
out = into
|
out = into
|
||||||
|
|
||||||
rc = _lib.aegis128x4_decrypt(
|
rc = _lib.aegis128x4_decrypt(
|
||||||
@@ -580,9 +582,7 @@ class Mac:
|
|||||||
out = into
|
out = into
|
||||||
|
|
||||||
clone = self.clone()
|
clone = self.clone()
|
||||||
rc = _lib.aegis128x4_mac_final(
|
rc = _lib.aegis128x4_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
|
||||||
)
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
@@ -708,17 +708,24 @@ class Encryptor:
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
"into length must be >= expected output size for this update"
|
"into length must be >= expected output size for this update"
|
||||||
)
|
)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis128x4_state_encrypt_update(
|
rc = _lib.aegis128x4_state_encrypt_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(message),
|
_ptr(message),
|
||||||
message.nbytes,
|
message.nbytes,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt update failed: {err_name}")
|
raise RuntimeError(
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||||
|
)
|
||||||
|
w = int(written[0])
|
||||||
|
assert w == expected_out
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||||
"""Finalize encryption and return the authentication tag.
|
"""Finalize encryption and return the authentication tag.
|
||||||
@@ -739,17 +746,24 @@ class Encryptor:
|
|||||||
if into is not None:
|
if into is not None:
|
||||||
into = memoryview(into)
|
into = memoryview(into)
|
||||||
out = into if into is not None else bytearray(maclen)
|
out = into if into is not None else bytearray(maclen)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis128x4_state_encrypt_final(
|
rc = _lib.aegis128x4_state_encrypt_final(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out),
|
ffi.from_buffer(out),
|
||||||
|
memoryview(out).nbytes,
|
||||||
|
written,
|
||||||
maclen,
|
maclen,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||||
|
w = int(written[0])
|
||||||
|
if into is None:
|
||||||
|
# Only the tag bytes are returned when we allocate the buffer
|
||||||
|
assert w == maclen
|
||||||
self._state = None
|
self._state = None
|
||||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Decryptor:
|
class Decryptor:
|
||||||
@@ -823,9 +837,12 @@ class Decryptor:
|
|||||||
out_mv = memoryview(out)
|
out_mv = memoryview(out)
|
||||||
if out_mv.nbytes < expected_out:
|
if out_mv.nbytes < expected_out:
|
||||||
raise TypeError("into length must be >= required capacity for this update")
|
raise TypeError("into length must be >= required capacity for this update")
|
||||||
rc = _lib.aegis128x4_state_decrypt_update(
|
written = ffi.new("size_t *")
|
||||||
|
rc = _lib.aegis128x4_state_decrypt_detached_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(ct),
|
_ptr(ct),
|
||||||
ct.nbytes,
|
ct.nbytes,
|
||||||
)
|
)
|
||||||
@@ -833,7 +850,9 @@ class Decryptor:
|
|||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
w = int(written[0])
|
||||||
|
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, mac: Buffer) -> None:
|
def final(self, mac: Buffer) -> None:
|
||||||
"""Finalize decryption by verifying the MAC tag.
|
"""Finalize decryption by verifying the MAC tag.
|
||||||
@@ -852,7 +871,9 @@ class Decryptor:
|
|||||||
mac = memoryview(mac)
|
mac = memoryview(mac)
|
||||||
if mac.nbytes != maclen:
|
if mac.nbytes != maclen:
|
||||||
raise TypeError(f"mac length must be {maclen}")
|
raise TypeError(f"mac length must be {maclen}")
|
||||||
rc = _lib.aegis128x4_state_decrypt_final(self._state.ptr, _ptr(mac), maclen)
|
rc = _lib.aegis128x4_state_decrypt_detached_final(
|
||||||
|
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||||
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise ValueError("authentication failed")
|
raise ValueError("authentication failed")
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -295,7 +295,9 @@ def decrypt(
|
|||||||
out = bytearray(expected_out)
|
out = bytearray(expected_out)
|
||||||
else:
|
else:
|
||||||
if into.nbytes < expected_out:
|
if into.nbytes < expected_out:
|
||||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
raise TypeError(
|
||||||
|
"into length must be at least ct.nbytes - maclen"
|
||||||
|
)
|
||||||
out = into
|
out = into
|
||||||
|
|
||||||
rc = _lib.aegis256_decrypt(
|
rc = _lib.aegis256_decrypt(
|
||||||
@@ -580,9 +582,7 @@ class Mac:
|
|||||||
out = into
|
out = into
|
||||||
|
|
||||||
clone = self.clone()
|
clone = self.clone()
|
||||||
rc = _lib.aegis256_mac_final(
|
rc = _lib.aegis256_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
|
||||||
)
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
@@ -708,17 +708,24 @@ class Encryptor:
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
"into length must be >= expected output size for this update"
|
"into length must be >= expected output size for this update"
|
||||||
)
|
)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis256_state_encrypt_update(
|
rc = _lib.aegis256_state_encrypt_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(message),
|
_ptr(message),
|
||||||
message.nbytes,
|
message.nbytes,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt update failed: {err_name}")
|
raise RuntimeError(
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||||
|
)
|
||||||
|
w = int(written[0])
|
||||||
|
assert w == expected_out
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||||
"""Finalize encryption and return the authentication tag.
|
"""Finalize encryption and return the authentication tag.
|
||||||
@@ -739,17 +746,24 @@ class Encryptor:
|
|||||||
if into is not None:
|
if into is not None:
|
||||||
into = memoryview(into)
|
into = memoryview(into)
|
||||||
out = into if into is not None else bytearray(maclen)
|
out = into if into is not None else bytearray(maclen)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis256_state_encrypt_final(
|
rc = _lib.aegis256_state_encrypt_final(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out),
|
ffi.from_buffer(out),
|
||||||
|
memoryview(out).nbytes,
|
||||||
|
written,
|
||||||
maclen,
|
maclen,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||||
|
w = int(written[0])
|
||||||
|
if into is None:
|
||||||
|
# Only the tag bytes are returned when we allocate the buffer
|
||||||
|
assert w == maclen
|
||||||
self._state = None
|
self._state = None
|
||||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Decryptor:
|
class Decryptor:
|
||||||
@@ -823,9 +837,12 @@ class Decryptor:
|
|||||||
out_mv = memoryview(out)
|
out_mv = memoryview(out)
|
||||||
if out_mv.nbytes < expected_out:
|
if out_mv.nbytes < expected_out:
|
||||||
raise TypeError("into length must be >= required capacity for this update")
|
raise TypeError("into length must be >= required capacity for this update")
|
||||||
rc = _lib.aegis256_state_decrypt_update(
|
written = ffi.new("size_t *")
|
||||||
|
rc = _lib.aegis256_state_decrypt_detached_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(ct),
|
_ptr(ct),
|
||||||
ct.nbytes,
|
ct.nbytes,
|
||||||
)
|
)
|
||||||
@@ -833,7 +850,9 @@ class Decryptor:
|
|||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
w = int(written[0])
|
||||||
|
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, mac: Buffer) -> None:
|
def final(self, mac: Buffer) -> None:
|
||||||
"""Finalize decryption by verifying the MAC tag.
|
"""Finalize decryption by verifying the MAC tag.
|
||||||
@@ -852,7 +871,9 @@ class Decryptor:
|
|||||||
mac = memoryview(mac)
|
mac = memoryview(mac)
|
||||||
if mac.nbytes != maclen:
|
if mac.nbytes != maclen:
|
||||||
raise TypeError(f"mac length must be {maclen}")
|
raise TypeError(f"mac length must be {maclen}")
|
||||||
rc = _lib.aegis256_state_decrypt_final(self._state.ptr, _ptr(mac), maclen)
|
rc = _lib.aegis256_state_decrypt_detached_final(
|
||||||
|
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||||
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise ValueError("authentication failed")
|
raise ValueError("authentication failed")
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -295,7 +295,9 @@ def decrypt(
|
|||||||
out = bytearray(expected_out)
|
out = bytearray(expected_out)
|
||||||
else:
|
else:
|
||||||
if into.nbytes < expected_out:
|
if into.nbytes < expected_out:
|
||||||
raise TypeError("into length must be at least ct.nbytes - maclen")
|
raise TypeError(
|
||||||
|
"into length must be at least ct.nbytes - maclen"
|
||||||
|
)
|
||||||
out = into
|
out = into
|
||||||
|
|
||||||
rc = _lib.aegis256x2_decrypt(
|
rc = _lib.aegis256x2_decrypt(
|
||||||
@@ -580,9 +582,7 @@ class Mac:
|
|||||||
out = into
|
out = into
|
||||||
|
|
||||||
clone = self.clone()
|
clone = self.clone()
|
||||||
rc = _lib.aegis256x2_mac_final(
|
rc = _lib.aegis256x2_mac_final(clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes)
|
||||||
clone._proxy.ptr, ffi.from_buffer(out), memoryview(out).nbytes
|
|
||||||
)
|
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
@@ -708,17 +708,24 @@ class Encryptor:
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
"into length must be >= expected output size for this update"
|
"into length must be >= expected output size for this update"
|
||||||
)
|
)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis256x2_state_encrypt_update(
|
rc = _lib.aegis256x2_state_encrypt_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(message),
|
_ptr(message),
|
||||||
message.nbytes,
|
message.nbytes,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt update failed: {err_name}")
|
raise RuntimeError(
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||||
|
)
|
||||||
|
w = int(written[0])
|
||||||
|
assert w == expected_out
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||||
"""Finalize encryption and return the authentication tag.
|
"""Finalize encryption and return the authentication tag.
|
||||||
@@ -739,17 +746,24 @@ class Encryptor:
|
|||||||
if into is not None:
|
if into is not None:
|
||||||
into = memoryview(into)
|
into = memoryview(into)
|
||||||
out = into if into is not None else bytearray(maclen)
|
out = into if into is not None else bytearray(maclen)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis256x2_state_encrypt_final(
|
rc = _lib.aegis256x2_state_encrypt_final(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out),
|
ffi.from_buffer(out),
|
||||||
|
memoryview(out).nbytes,
|
||||||
|
written,
|
||||||
maclen,
|
maclen,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||||
|
w = int(written[0])
|
||||||
|
if into is None:
|
||||||
|
# Only the tag bytes are returned when we allocate the buffer
|
||||||
|
assert w == maclen
|
||||||
self._state = None
|
self._state = None
|
||||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Decryptor:
|
class Decryptor:
|
||||||
@@ -823,9 +837,12 @@ class Decryptor:
|
|||||||
out_mv = memoryview(out)
|
out_mv = memoryview(out)
|
||||||
if out_mv.nbytes < expected_out:
|
if out_mv.nbytes < expected_out:
|
||||||
raise TypeError("into length must be >= required capacity for this update")
|
raise TypeError("into length must be >= required capacity for this update")
|
||||||
rc = _lib.aegis256x2_state_decrypt_update(
|
written = ffi.new("size_t *")
|
||||||
|
rc = _lib.aegis256x2_state_decrypt_detached_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(ct),
|
_ptr(ct),
|
||||||
ct.nbytes,
|
ct.nbytes,
|
||||||
)
|
)
|
||||||
@@ -833,7 +850,9 @@ class Decryptor:
|
|||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
w = int(written[0])
|
||||||
|
assert w == expected_out, f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, mac: Buffer) -> None:
|
def final(self, mac: Buffer) -> None:
|
||||||
"""Finalize decryption by verifying the MAC tag.
|
"""Finalize decryption by verifying the MAC tag.
|
||||||
@@ -852,7 +871,9 @@ class Decryptor:
|
|||||||
mac = memoryview(mac)
|
mac = memoryview(mac)
|
||||||
if mac.nbytes != maclen:
|
if mac.nbytes != maclen:
|
||||||
raise TypeError(f"mac length must be {maclen}")
|
raise TypeError(f"mac length must be {maclen}")
|
||||||
rc = _lib.aegis256x2_state_decrypt_final(self._state.ptr, _ptr(mac), maclen)
|
rc = _lib.aegis256x2_state_decrypt_detached_final(
|
||||||
|
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||||
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise ValueError("authentication failed")
|
raise ValueError("authentication failed")
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -708,17 +708,24 @@ class Encryptor:
|
|||||||
raise TypeError(
|
raise TypeError(
|
||||||
"into length must be >= expected output size for this update"
|
"into length must be >= expected output size for this update"
|
||||||
)
|
)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis256x4_state_encrypt_update(
|
rc = _lib.aegis256x4_state_encrypt_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(message),
|
_ptr(message),
|
||||||
message.nbytes,
|
message.nbytes,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt update failed: {err_name}")
|
raise RuntimeError(
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
f"state encrypt update failed: {err_name} written {written[0]}"
|
||||||
|
)
|
||||||
|
w = int(written[0])
|
||||||
|
assert w == expected_out
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
def final(self, into: Buffer | None = None) -> bytearray | memoryview:
|
||||||
"""Finalize encryption and return the authentication tag.
|
"""Finalize encryption and return the authentication tag.
|
||||||
@@ -739,17 +746,24 @@ class Encryptor:
|
|||||||
if into is not None:
|
if into is not None:
|
||||||
into = memoryview(into)
|
into = memoryview(into)
|
||||||
out = into if into is not None else bytearray(maclen)
|
out = into if into is not None else bytearray(maclen)
|
||||||
|
written = ffi.new("size_t *")
|
||||||
rc = _lib.aegis256x4_state_encrypt_final(
|
rc = _lib.aegis256x4_state_encrypt_final(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out),
|
ffi.from_buffer(out),
|
||||||
|
memoryview(out).nbytes,
|
||||||
|
written,
|
||||||
maclen,
|
maclen,
|
||||||
)
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
raise RuntimeError(f"state encrypt final failed: {err_name}")
|
||||||
|
w = int(written[0])
|
||||||
|
if into is None:
|
||||||
|
# Only the tag bytes are returned when we allocate the buffer
|
||||||
|
assert w == maclen
|
||||||
self._state = None
|
self._state = None
|
||||||
return out if into is None else memoryview(out)[:maclen] # type: ignore
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
|
|
||||||
class Decryptor:
|
class Decryptor:
|
||||||
@@ -823,9 +837,12 @@ class Decryptor:
|
|||||||
out_mv = memoryview(out)
|
out_mv = memoryview(out)
|
||||||
if out_mv.nbytes < expected_out:
|
if out_mv.nbytes < expected_out:
|
||||||
raise TypeError("into length must be >= required capacity for this update")
|
raise TypeError("into length must be >= required capacity for this update")
|
||||||
rc = _lib.aegis256x4_state_decrypt_update(
|
written = ffi.new("size_t *")
|
||||||
|
rc = _lib.aegis256x4_state_decrypt_detached_update(
|
||||||
self._state.ptr,
|
self._state.ptr,
|
||||||
ffi.from_buffer(out_mv),
|
ffi.from_buffer(out_mv),
|
||||||
|
out_mv.nbytes,
|
||||||
|
written,
|
||||||
_ptr(ct),
|
_ptr(ct),
|
||||||
ct.nbytes,
|
ct.nbytes,
|
||||||
)
|
)
|
||||||
@@ -833,7 +850,11 @@ class Decryptor:
|
|||||||
err_num = ffi.errno
|
err_num = ffi.errno
|
||||||
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
err_name = errno.errorcode.get(err_num, f"errno_{err_num}")
|
||||||
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
raise RuntimeError(f"state decrypt update failed: {err_name}")
|
||||||
return out if into is None else memoryview(out)[:expected_out] # type: ignore
|
w = int(written[0])
|
||||||
|
assert w == expected_out, (
|
||||||
|
f"got {w}, expected {expected_out}, ct.nbytes={ct.nbytes}"
|
||||||
|
)
|
||||||
|
return out if into is None else memoryview(out)[:w] # type: ignore
|
||||||
|
|
||||||
def final(self, mac: Buffer) -> None:
|
def final(self, mac: Buffer) -> None:
|
||||||
"""Finalize decryption by verifying the MAC tag.
|
"""Finalize decryption by verifying the MAC tag.
|
||||||
@@ -852,7 +873,9 @@ class Decryptor:
|
|||||||
mac = memoryview(mac)
|
mac = memoryview(mac)
|
||||||
if mac.nbytes != maclen:
|
if mac.nbytes != maclen:
|
||||||
raise TypeError(f"mac length must be {maclen}")
|
raise TypeError(f"mac length must be {maclen}")
|
||||||
rc = _lib.aegis256x4_state_decrypt_final(self._state.ptr, _ptr(mac), maclen)
|
rc = _lib.aegis256x4_state_decrypt_detached_final(
|
||||||
|
self._state.ptr, ffi.NULL, 0, ffi.NULL, _ptr(mac), maclen
|
||||||
|
)
|
||||||
if rc != 0:
|
if rc != 0:
|
||||||
raise ValueError("authentication failed")
|
raise ValueError("authentication failed")
|
||||||
self._state = None
|
self._state = None
|
||||||
@@ -55,10 +55,35 @@ void aegis128l_state_init(aegis128l_state *st_,
|
|||||||
size_t adlen,
|
size_t adlen,
|
||||||
const uint8_t *npub,
|
const uint8_t *npub,
|
||||||
const uint8_t *k);
|
const uint8_t *k);
|
||||||
int aegis128l_state_encrypt_update(aegis128l_state *st_, uint8_t *c, const uint8_t *m, size_t mlen);
|
int aegis128l_state_encrypt_update(aegis128l_state *st_,
|
||||||
int aegis128l_state_encrypt_final(aegis128l_state *st_, uint8_t *mac, size_t maclen);
|
uint8_t *c,
|
||||||
int aegis128l_state_decrypt_update(aegis128l_state *st_, uint8_t *m, const uint8_t *c, size_t clen) ;
|
size_t clen_max,
|
||||||
int aegis128l_state_decrypt_final(aegis128l_state *st_, const uint8_t *mac, size_t maclen) ;
|
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_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||||
void aegis128l_encrypt_unauthenticated(uint8_t *c,
|
void aegis128l_encrypt_unauthenticated(uint8_t *c,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
@@ -126,14 +151,33 @@ void aegis128x2_state_init(aegis128x2_state *st_,
|
|||||||
const uint8_t *k);
|
const uint8_t *k);
|
||||||
int aegis128x2_state_encrypt_update(aegis128x2_state *st_,
|
int aegis128x2_state_encrypt_update(aegis128x2_state *st_,
|
||||||
uint8_t *c,
|
uint8_t *c,
|
||||||
|
size_t clen_max,
|
||||||
|
size_t *written,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
size_t mlen);
|
size_t mlen);
|
||||||
int aegis128x2_state_encrypt_final(aegis128x2_state *st_, uint8_t *mac, size_t maclen);
|
int aegis128x2_state_encrypt_detached_final(aegis128x2_state *st_,
|
||||||
int aegis128x2_state_decrypt_update(aegis128x2_state *st_,
|
uint8_t *c,
|
||||||
uint8_t *m,
|
size_t clen_max,
|
||||||
const uint8_t *c,
|
size_t *written,
|
||||||
size_t clen) ;
|
uint8_t *mac,
|
||||||
int aegis128x2_state_decrypt_final(aegis128x2_state *st_, const uint8_t *mac, size_t maclen) ;
|
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_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||||
void aegis128x2_encrypt_unauthenticated(uint8_t *c,
|
void aegis128x2_encrypt_unauthenticated(uint8_t *c,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
@@ -201,14 +245,33 @@ void aegis128x4_state_init(aegis128x4_state *st_,
|
|||||||
const uint8_t *k);
|
const uint8_t *k);
|
||||||
int aegis128x4_state_encrypt_update(aegis128x4_state *st_,
|
int aegis128x4_state_encrypt_update(aegis128x4_state *st_,
|
||||||
uint8_t *c,
|
uint8_t *c,
|
||||||
|
size_t clen_max,
|
||||||
|
size_t *written,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
size_t mlen);
|
size_t mlen);
|
||||||
int aegis128x4_state_encrypt_final(aegis128x4_state *st_, uint8_t *mac, size_t maclen);
|
int aegis128x4_state_encrypt_detached_final(aegis128x4_state *st_,
|
||||||
int aegis128x4_state_decrypt_update(aegis128x4_state *st_,
|
uint8_t *c,
|
||||||
uint8_t *m,
|
size_t clen_max,
|
||||||
const uint8_t *c,
|
size_t *written,
|
||||||
size_t clen) ;
|
uint8_t *mac,
|
||||||
int aegis128x4_state_decrypt_final(aegis128x4_state *st_, const uint8_t *mac, size_t maclen) ;
|
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_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||||
void aegis128x4_encrypt_unauthenticated(uint8_t *c,
|
void aegis128x4_encrypt_unauthenticated(uint8_t *c,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
@@ -274,10 +337,35 @@ void aegis256_state_init(aegis256_state *st_,
|
|||||||
size_t adlen,
|
size_t adlen,
|
||||||
const uint8_t *npub,
|
const uint8_t *npub,
|
||||||
const uint8_t *k);
|
const uint8_t *k);
|
||||||
int aegis256_state_encrypt_update(aegis256_state *st_, uint8_t *c, const uint8_t *m, size_t mlen);
|
int aegis256_state_encrypt_update(aegis256_state *st_,
|
||||||
int aegis256_state_encrypt_final(aegis256_state *st_, uint8_t *mac, size_t maclen);
|
uint8_t *c,
|
||||||
int aegis256_state_decrypt_update(aegis256_state *st_, uint8_t *m, const uint8_t *c, size_t clen) ;
|
size_t clen_max,
|
||||||
int aegis256_state_decrypt_final(aegis256_state *st_, const uint8_t *mac, size_t maclen) ;
|
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_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||||
void aegis256_encrypt_unauthenticated(uint8_t *c,
|
void aegis256_encrypt_unauthenticated(uint8_t *c,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
@@ -345,14 +433,33 @@ void aegis256x2_state_init(aegis256x2_state *st_,
|
|||||||
const uint8_t *k);
|
const uint8_t *k);
|
||||||
int aegis256x2_state_encrypt_update(aegis256x2_state *st_,
|
int aegis256x2_state_encrypt_update(aegis256x2_state *st_,
|
||||||
uint8_t *c,
|
uint8_t *c,
|
||||||
|
size_t clen_max,
|
||||||
|
size_t *written,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
size_t mlen);
|
size_t mlen);
|
||||||
int aegis256x2_state_encrypt_final(aegis256x2_state *st_, uint8_t *mac, size_t maclen);
|
int aegis256x2_state_encrypt_detached_final(aegis256x2_state *st_,
|
||||||
int aegis256x2_state_decrypt_update(aegis256x2_state *st_,
|
uint8_t *c,
|
||||||
uint8_t *m,
|
size_t clen_max,
|
||||||
const uint8_t *c,
|
size_t *written,
|
||||||
size_t clen) ;
|
uint8_t *mac,
|
||||||
int aegis256x2_state_decrypt_final(aegis256x2_state *st_, const uint8_t *mac, size_t maclen) ;
|
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_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||||
void aegis256x2_encrypt_unauthenticated(uint8_t *c,
|
void aegis256x2_encrypt_unauthenticated(uint8_t *c,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
@@ -420,14 +527,33 @@ void aegis256x4_state_init(aegis256x4_state *st_,
|
|||||||
const uint8_t *k);
|
const uint8_t *k);
|
||||||
int aegis256x4_state_encrypt_update(aegis256x4_state *st_,
|
int aegis256x4_state_encrypt_update(aegis256x4_state *st_,
|
||||||
uint8_t *c,
|
uint8_t *c,
|
||||||
|
size_t clen_max,
|
||||||
|
size_t *written,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
size_t mlen);
|
size_t mlen);
|
||||||
int aegis256x4_state_encrypt_final(aegis256x4_state *st_, uint8_t *mac, size_t maclen);
|
int aegis256x4_state_encrypt_detached_final(aegis256x4_state *st_,
|
||||||
int aegis256x4_state_decrypt_update(aegis256x4_state *st_,
|
uint8_t *c,
|
||||||
uint8_t *m,
|
size_t clen_max,
|
||||||
const uint8_t *c,
|
size_t *written,
|
||||||
size_t clen) ;
|
uint8_t *mac,
|
||||||
int aegis256x4_state_decrypt_final(aegis256x4_state *st_, const uint8_t *mac, size_t maclen) ;
|
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_stream(uint8_t *out, size_t len, const uint8_t *npub, const uint8_t *k);
|
||||||
void aegis256x4_encrypt_unauthenticated(uint8_t *c,
|
void aegis256x4_encrypt_unauthenticated(uint8_t *c,
|
||||||
const uint8_t *m,
|
const uint8_t *m,
|
||||||
@@ -12,7 +12,7 @@ Output format and throughput units mirror the Zig benchmark (Mb/s).
|
|||||||
import secrets
|
import secrets
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from aeg import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
from pyaegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
||||||
|
|
||||||
MSG_LEN = 16384000 # 16 000 KiB
|
MSG_LEN = 16384000 # 16 000 KiB
|
||||||
ITERATIONS = 100
|
ITERATIONS = 100
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
"""Utility helpers for aeg.
|
"""Utility helpers for pyaegis.
|
||||||
|
|
||||||
Currently provides Python-side aligned allocation helpers that avoid relying
|
Currently provides Python-side aligned allocation helpers that avoid relying
|
||||||
on libc/posix_memalign. Memory is owned by Python; C code only borrows it.
|
on libc/posix_memalign. Memory is owned by Python; C code only borrows it.
|
||||||
+9
-16
@@ -1,16 +1,16 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools>=61.0", "cffi>=2.0.0", "setuptools-scm>=8.0"]
|
requires = ["setuptools>=61.0", "cffi>=2.0.0"]
|
||||||
build-backend = "build_backend"
|
build-backend = "build_backend"
|
||||||
backend-path = ["tools"]
|
backend-path = ["tools"]
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "aeg"
|
name = "pyaegis"
|
||||||
dynamic = ["version"]
|
version = "0.3.1"
|
||||||
description = "AEGIS encryption easy to use Python binding. Wheels for major platforms."
|
description = "Python bindings for libaegis"
|
||||||
readme = {file = "README.md", content-type = "text/markdown"}
|
|
||||||
requires-python = ">=3.10"
|
requires-python = ">=3.10"
|
||||||
classifiers = [
|
classifiers = [
|
||||||
"Development Status :: 5 - Production/Stable",
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Operating System :: OS Independent",
|
"Operating System :: OS Independent",
|
||||||
"Topic :: Security :: Cryptography",
|
"Topic :: Security :: Cryptography",
|
||||||
@@ -20,23 +20,16 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[project.urls]
|
[project.urls]
|
||||||
Homepage = "https://git.zi.fi/LeoVasanko/aegis-python"
|
Homepage = "https://github.com/LeoVasanko/pyaegis"
|
||||||
Repository = "https://github.com/LeoVasanko/aegis-python"
|
|
||||||
|
|
||||||
[dependency-groups]
|
[dependency-groups]
|
||||||
dev = [
|
dev = [
|
||||||
"auditwheel>=6.5.0",
|
|
||||||
"pytest>=8.4.2",
|
"pytest>=8.4.2",
|
||||||
"ruff>=0.14.4",
|
|
||||||
"setuptools>=80.9.0",
|
"setuptools>=80.9.0",
|
||||||
"setuptools-scm>=9.2.2",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[tool.setuptools]
|
[tool.setuptools]
|
||||||
package-dir = {"" = "src"}
|
packages = ["pyaegis"]
|
||||||
packages = ["aeg"]
|
|
||||||
|
|
||||||
[tool.setuptools.package-data]
|
[tool.setuptools.package-data]
|
||||||
aeg = ["*.h"]
|
pyaegis = ["*.h", "*.so", "*.pyd"]
|
||||||
|
|
||||||
[tool.setuptools_scm]
|
|
||||||
|
|||||||
@@ -1,24 +1,49 @@
|
|||||||
"""Setup script for aeg - builds CFFI extension with libaegis C library."""
|
"""Setup script for pyaegis - builds CFFI extension linking to libaegis.a"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import sysconfig
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from cffi import FFI
|
from cffi import FFI
|
||||||
from setuptools import setup
|
from setuptools import setup
|
||||||
|
|
||||||
libaegis_static = Path("libaegis/zig-out/lib") / (
|
|
||||||
"aegis.lib" if sys.platform == "win32" else "libaegis.a"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
def find_libaegis():
|
||||||
|
"""Locate libaegis.a - check common locations."""
|
||||||
|
libaegis_paths = [
|
||||||
|
Path("libaegis/zig-out/lib/libaegis.a"), # Zig build output (repo build)
|
||||||
|
Path("libaegis/build/libaegis.a"), # CMake build output (repo build)
|
||||||
|
Path("/usr/local/lib/libaegis.a"), # System install
|
||||||
|
Path("/usr/lib/libaegis.a"), # System install
|
||||||
|
]
|
||||||
|
|
||||||
|
for path in libaegis_paths:
|
||||||
|
if path.exists():
|
||||||
|
print(f"Found libaegis.a at: {path.resolve()}")
|
||||||
|
return str(path.resolve())
|
||||||
|
|
||||||
|
# Return None instead of raising - will be caught during build
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Read the CDEF header
|
||||||
|
cdef_path = Path(__file__).parent / "pyaegis" / "aegis_cdef.h"
|
||||||
|
cdef_content = cdef_path.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
# Create CFFI builder
|
||||||
ffibuilder = FFI()
|
ffibuilder = FFI()
|
||||||
ffibuilder.cdef((Path(__file__).parent / "src/aeg/aegis_cdef.h").read_text())
|
ffibuilder.cdef(cdef_content)
|
||||||
|
|
||||||
# Free-threaded Python does not support Limited API (abi3)
|
# Include directory for headers
|
||||||
is_free_threaded = sysconfig.get_config_var("Py_GIL_DISABLED")
|
include_dirs = []
|
||||||
|
libaegis_include = Path("libaegis/src/include")
|
||||||
|
if libaegis_include.exists():
|
||||||
|
include_dirs.append(str(libaegis_include.resolve()))
|
||||||
|
|
||||||
|
# Try to find libaegis.a, but don't fail if not found (build backend will build it)
|
||||||
|
libaegis_static = find_libaegis()
|
||||||
|
|
||||||
|
# Set the source
|
||||||
ffibuilder.set_source(
|
ffibuilder.set_source(
|
||||||
"aeg._aegis",
|
"pyaegis._aegis", # module name
|
||||||
"""
|
"""
|
||||||
#include "aegis.h"
|
#include "aegis.h"
|
||||||
#include "aegis128l.h"
|
#include "aegis128l.h"
|
||||||
@@ -28,15 +53,11 @@ ffibuilder.set_source(
|
|||||||
#include "aegis256x2.h"
|
#include "aegis256x2.h"
|
||||||
#include "aegis256x4.h"
|
#include "aegis256x4.h"
|
||||||
""",
|
""",
|
||||||
include_dirs=["libaegis/src/include"],
|
include_dirs=include_dirs,
|
||||||
extra_objects=[str(libaegis_static.resolve())],
|
extra_objects=[libaegis_static] if libaegis_static else [],
|
||||||
py_limited_api=not is_free_threaded,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
setup(
|
setup(
|
||||||
cffi_modules=["setup.py:ffibuilder"],
|
cffi_modules=["setup.py:ffibuilder"],
|
||||||
options=(
|
|
||||||
{"bdist_wheel": {"py_limited_api": "cp310"}} if not is_free_threaded else {}
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import importlib
|
|
||||||
|
|
||||||
from ._ciphers import CIPHERS, CipherName
|
|
||||||
from ._typing import Cipher
|
|
||||||
|
|
||||||
__all__ = ["cipher", "CIPHERS", "Cipher", "CipherName"]
|
|
||||||
|
|
||||||
|
|
||||||
def cipher(alg: CipherName) -> Cipher:
|
|
||||||
"""Acquire a cipher module by name."""
|
|
||||||
name = alg.lower().replace("-", "")
|
|
||||||
if name == "aegis128":
|
|
||||||
name = "aegis128l" # AEGIS-128 is dead, the user meant AEGIS-128L
|
|
||||||
if not name.startswith("aegis"):
|
|
||||||
name = "aegis" + name
|
|
||||||
if name in CIPHERS.values():
|
|
||||||
return importlib.import_module(f".{name}", __package__) # type: ignore[return-value]
|
|
||||||
raise ValueError(f"Unknown algorithm {alg!r}. Valid options: {', '.join(CIPHERS)}")
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
# This file is generated by tools/generate.py. Do not edit.
|
|
||||||
from typing import Literal
|
|
||||||
|
|
||||||
CipherName = Literal[
|
|
||||||
"AEGIS-128L",
|
|
||||||
"AEGIS-128X2",
|
|
||||||
"AEGIS-128X4",
|
|
||||||
"AEGIS-256",
|
|
||||||
"AEGIS-256X2",
|
|
||||||
"AEGIS-256X4",
|
|
||||||
]
|
|
||||||
|
|
||||||
CIPHERS: dict[CipherName, str] = {
|
|
||||||
"AEGIS-128L": "aegis128l",
|
|
||||||
"AEGIS-128X2": "aegis128x2",
|
|
||||||
"AEGIS-128X4": "aegis128x4",
|
|
||||||
"AEGIS-256": "aegis256",
|
|
||||||
"AEGIS-256X2": "aegis256x2",
|
|
||||||
"AEGIS-256X4": "aegis256x4",
|
|
||||||
}
|
|
||||||
@@ -1,126 +0,0 @@
|
|||||||
from typing import TYPE_CHECKING, Protocol
|
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
|
||||||
from .util import Buffer
|
|
||||||
|
|
||||||
__all__ = ["Cipher"]
|
|
||||||
|
|
||||||
|
|
||||||
class _Mac(Protocol):
|
|
||||||
def reset(self) -> None: ...
|
|
||||||
def clone(self) -> "_Mac": ...
|
|
||||||
def update(self, data: "Buffer") -> None: ...
|
|
||||||
def final(self, into: "Buffer | None" = None) -> bytearray | memoryview: ...
|
|
||||||
def digest(self) -> bytes: ...
|
|
||||||
def hexdigest(self) -> str: ...
|
|
||||||
def verify(self, mac: "Buffer") -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
class _Encryptor(Protocol):
|
|
||||||
def update(
|
|
||||||
self, message: "Buffer", into: "Buffer | None" = None
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
def final(self, into: "Buffer | None" = None) -> bytearray | memoryview: ...
|
|
||||||
|
|
||||||
|
|
||||||
class _Decryptor(Protocol):
|
|
||||||
def update(
|
|
||||||
self, ct: "Buffer", into: "Buffer | None" = None
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
def final(self, mac: "Buffer") -> None: ...
|
|
||||||
|
|
||||||
|
|
||||||
class Cipher(Protocol):
|
|
||||||
NAME: str
|
|
||||||
KEYBYTES: int
|
|
||||||
NONCEBYTES: int
|
|
||||||
MACBYTES: int
|
|
||||||
MACBYTES_LONG: int
|
|
||||||
ALIGNMENT: int
|
|
||||||
RATE: int
|
|
||||||
|
|
||||||
Mac: type[_Mac]
|
|
||||||
Encryptor: type[_Encryptor]
|
|
||||||
Decryptor: type[_Decryptor]
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def random_key() -> bytearray: ...
|
|
||||||
@staticmethod
|
|
||||||
def random_nonce() -> bytearray: ...
|
|
||||||
@staticmethod
|
|
||||||
def encrypt_detached(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer",
|
|
||||||
message: "Buffer",
|
|
||||||
ad: "Buffer | None" = None,
|
|
||||||
*,
|
|
||||||
maclen: int = ...,
|
|
||||||
ct_into: "Buffer | None" = None,
|
|
||||||
mac_into: "Buffer | None" = None,
|
|
||||||
) -> tuple[bytearray | memoryview, bytearray | memoryview]: ...
|
|
||||||
@staticmethod
|
|
||||||
def decrypt_detached(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer",
|
|
||||||
ct: "Buffer",
|
|
||||||
mac: "Buffer",
|
|
||||||
ad: "Buffer | None" = None,
|
|
||||||
*,
|
|
||||||
into: "Buffer | None" = None,
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
@staticmethod
|
|
||||||
def encrypt(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer",
|
|
||||||
message: "Buffer",
|
|
||||||
ad: "Buffer | None" = None,
|
|
||||||
*,
|
|
||||||
maclen: int = ...,
|
|
||||||
into: "Buffer | None" = None,
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
@staticmethod
|
|
||||||
def decrypt(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer",
|
|
||||||
ct: "Buffer",
|
|
||||||
ad: "Buffer | None" = None,
|
|
||||||
*,
|
|
||||||
maclen: int = ...,
|
|
||||||
into: "Buffer | None" = None,
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
@staticmethod
|
|
||||||
def stream(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer | None",
|
|
||||||
length: int | None = None,
|
|
||||||
*,
|
|
||||||
into: "Buffer | None" = None,
|
|
||||||
) -> "bytearray | Buffer": ...
|
|
||||||
@staticmethod
|
|
||||||
def encrypt_unauthenticated(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer",
|
|
||||||
message: "Buffer",
|
|
||||||
*,
|
|
||||||
into: "Buffer | None" = None,
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
@staticmethod
|
|
||||||
def decrypt_unauthenticated(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer",
|
|
||||||
ct: "Buffer",
|
|
||||||
*,
|
|
||||||
into: "Buffer | None" = None,
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
@staticmethod
|
|
||||||
def mac(
|
|
||||||
key: "Buffer",
|
|
||||||
nonce: "Buffer",
|
|
||||||
data: "Buffer",
|
|
||||||
maclen: int = ...,
|
|
||||||
into: "Buffer | None" = None,
|
|
||||||
) -> bytearray | memoryview: ...
|
|
||||||
@staticmethod
|
|
||||||
def nonce_increment(nonce: "Buffer") -> None: ...
|
|
||||||
@staticmethod
|
|
||||||
def wipe(buffer: "Buffer") -> None: ...
|
|
||||||
Executable
+89
@@ -0,0 +1,89 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
import json
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
def tvdump(topic, tvs):
|
||||||
|
with open(filename(topic), "w") as f:
|
||||||
|
f.write(json.dumps(tvs, indent=2))
|
||||||
|
|
||||||
|
print(json.dumps(tvs, indent=2))
|
||||||
|
|
||||||
|
|
||||||
|
def filename(topic):
|
||||||
|
return re.sub(r"[^a-z0-9]+", "-", topic.lower()) + ".json"
|
||||||
|
|
||||||
|
|
||||||
|
header = True
|
||||||
|
in_tv = False
|
||||||
|
tv = {}
|
||||||
|
tvs = []
|
||||||
|
must_fail = False
|
||||||
|
with open("../draft-irtf-cfrg-aegis-aead.md") as f:
|
||||||
|
for line in f:
|
||||||
|
line = line.strip()
|
||||||
|
if line == "":
|
||||||
|
continue
|
||||||
|
if line.startswith("# Test Vectors"):
|
||||||
|
header = False
|
||||||
|
continue
|
||||||
|
if header:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith("## "):
|
||||||
|
if len(tvs) > 0:
|
||||||
|
tvdump(topic, tvs)
|
||||||
|
topic = line[3:]
|
||||||
|
tv_name = topic
|
||||||
|
tvs = []
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.startswith("### "):
|
||||||
|
tv_name = line[4:]
|
||||||
|
tv = {"test": tv_name}
|
||||||
|
in_tv = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line == "~~~ test-vectors":
|
||||||
|
in_tv = True
|
||||||
|
tv = {"name": tv_name}
|
||||||
|
if must_fail:
|
||||||
|
tv["error"] = "verification failed"
|
||||||
|
must_fail = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line == "~~~":
|
||||||
|
tvs.append(tv)
|
||||||
|
in_tv = False
|
||||||
|
current_key = None
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.find("verification failed") != -1:
|
||||||
|
must_fail = True
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line == "After initialization:":
|
||||||
|
tv_name = tv_name + " (after initialization)"
|
||||||
|
|
||||||
|
if not in_tv:
|
||||||
|
continue
|
||||||
|
|
||||||
|
parts = line.split(":")
|
||||||
|
if len(parts) == 2:
|
||||||
|
key = parts[0].strip()
|
||||||
|
value = parts[1].strip()
|
||||||
|
if key == "After Update":
|
||||||
|
continue
|
||||||
|
if key in tv:
|
||||||
|
key = key + "_2"
|
||||||
|
tv[key] = value
|
||||||
|
current_key = key
|
||||||
|
continue
|
||||||
|
|
||||||
|
if not current_key:
|
||||||
|
continue
|
||||||
|
|
||||||
|
tv[key] += line.strip()
|
||||||
|
|
||||||
|
tvdump(topic, tvs)
|
||||||
@@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aeg import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
from pyaegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
||||||
|
|
||||||
from .util import random_split_bytes
|
from .util import random_split_bytes
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aeg import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
from pyaegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
||||||
|
|
||||||
from .util import random_split_bytes
|
from .util import random_split_bytes
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ after calling final(), preventing accidental misuse.
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from aeg import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
from pyaegis import aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4
|
||||||
|
|
||||||
# All AEGIS algorithm modules
|
# All AEGIS algorithm modules
|
||||||
ALL_ALGORITHMS = [aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4]
|
ALL_ALGORITHMS = [aegis128l, aegis128x2, aegis128x4, aegis256, aegis256x2, aegis256x4]
|
||||||
|
|||||||
+78
-46
@@ -1,68 +1,100 @@
|
|||||||
"""Custom build backend that builds libaegis with Zig before building the Python package."""
|
"""Custom build backend that builds libaegis with Zig before building the Python package."""
|
||||||
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
from setuptools import build_meta
|
from setuptools import build_meta as _orig
|
||||||
|
|
||||||
__all__ = [
|
|
||||||
"build_sdist",
|
|
||||||
"build_wheel",
|
|
||||||
"build_editable",
|
|
||||||
"get_requires_for_build_sdist",
|
|
||||||
"get_requires_for_build_wheel",
|
|
||||||
"prepare_metadata_for_build_wheel",
|
|
||||||
]
|
|
||||||
|
|
||||||
_MACOS_TARGET = "11.0"
|
|
||||||
_prepared = False
|
|
||||||
|
|
||||||
|
|
||||||
def _prepare():
|
def _check_zig_available():
|
||||||
"""Prepare the build environment and build libaegis."""
|
"""Check if Zig is installed and available."""
|
||||||
global _prepared
|
|
||||||
if _prepared:
|
|
||||||
return
|
|
||||||
_prepared = True
|
|
||||||
|
|
||||||
# Set macOS deployment target
|
|
||||||
if sys.platform == "darwin" and "MACOSX_DEPLOYMENT_TARGET" not in os.environ:
|
|
||||||
os.environ["MACOSX_DEPLOYMENT_TARGET"] = _MACOS_TARGET
|
|
||||||
|
|
||||||
# Check Zig is available
|
|
||||||
if shutil.which("zig") is None:
|
if shutil.which("zig") is None:
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
"Zig compiler not found. Install from https://ziglang.org/download/"
|
"\n" + "=" * 70 + "\n"
|
||||||
|
"ERROR: Zig compiler not found!\n"
|
||||||
|
"\n"
|
||||||
|
"Building pyaegis requires the Zig compiler to build the libaegis\n"
|
||||||
|
"static library. Please install Zig before building this package.\n"
|
||||||
|
"\n"
|
||||||
|
"Installation instructions:\n"
|
||||||
|
" - Visit: https://ziglang.org/download/\n"
|
||||||
|
" - Or use a package manager:\n"
|
||||||
|
" * macOS: brew install zig\n"
|
||||||
|
" * Linux: See https://github.com/ziglang/zig/wiki/Install-Zig-from-a-Package-Manager\n"
|
||||||
|
" * Windows: choco install zig or scoop install zig\n"
|
||||||
|
"\n"
|
||||||
|
"After installing Zig, please try building again.\n" + "=" * 70 + "\n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build libaegis
|
|
||||||
|
def _build_libaegis():
|
||||||
|
"""Build libaegis static library with Zig."""
|
||||||
|
# Check Zig availability first
|
||||||
|
_check_zig_available()
|
||||||
|
|
||||||
libaegis_dir = Path(__file__).parent.parent / "libaegis"
|
libaegis_dir = Path(__file__).parent.parent / "libaegis"
|
||||||
cmd = ["zig", "build", "-Drelease"]
|
if not libaegis_dir.exists():
|
||||||
if sys.platform == "darwin":
|
raise FileNotFoundError(
|
||||||
arch = {"arm64": "aarch64", "x86_64": "x86_64"}.get(platform.machine())
|
f"libaegis directory not found at {libaegis_dir}. "
|
||||||
if arch:
|
"Cannot build static library."
|
||||||
cmd.append(f"-Dtarget={arch}-macos.{_MACOS_TARGET}")
|
)
|
||||||
subprocess.run(cmd, cwd=libaegis_dir, check=True)
|
|
||||||
|
print("Building libaegis static library with Zig...")
|
||||||
|
try:
|
||||||
|
subprocess.run(
|
||||||
|
["zig", "build", "-Drelease"],
|
||||||
|
cwd=libaegis_dir,
|
||||||
|
check=True,
|
||||||
|
capture_output=False,
|
||||||
|
)
|
||||||
|
print("Successfully built libaegis static library")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(
|
||||||
|
f"\nError: Zig build failed with exit code {e.returncode}\n"
|
||||||
|
f"Command: {' '.join(e.cmd)}\n",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
build_sdist = build_meta.build_sdist
|
# Expose all the standard build backend hooks
|
||||||
get_requires_for_build_sdist = build_meta.get_requires_for_build_sdist
|
def get_requires_for_build_wheel(config_settings=None):
|
||||||
get_requires_for_build_wheel = build_meta.get_requires_for_build_wheel
|
"""Return build requirements and ensure libaegis is built first."""
|
||||||
prepare_metadata_for_build_wheel = build_meta.prepare_metadata_for_build_wheel
|
_build_libaegis()
|
||||||
|
return _orig.get_requires_for_build_wheel(config_settings)
|
||||||
|
|
||||||
|
|
||||||
|
def get_requires_for_build_sdist(config_settings=None):
|
||||||
|
"""Return build requirements for sdist and ensure libaegis is built first."""
|
||||||
|
_build_libaegis()
|
||||||
|
return _orig.get_requires_for_build_sdist(config_settings)
|
||||||
|
|
||||||
|
|
||||||
|
_orig_prepare_metadata_for_build_wheel = _orig.prepare_metadata_for_build_wheel
|
||||||
|
_orig_build_sdist = _orig.build_sdist
|
||||||
|
|
||||||
|
|
||||||
|
def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None):
|
||||||
|
"""Prepare metadata and ensure libaegis is built (some frontends call this early)."""
|
||||||
|
_build_libaegis()
|
||||||
|
return _orig_prepare_metadata_for_build_wheel(metadata_directory, config_settings)
|
||||||
|
|
||||||
|
|
||||||
|
def build_sdist(sdist_directory, config_settings=None):
|
||||||
|
"""Build sdist, building libaegis first so the sdist can include built artifacts if needed."""
|
||||||
|
_build_libaegis()
|
||||||
|
return _orig_build_sdist(sdist_directory, config_settings)
|
||||||
|
|
||||||
|
|
||||||
# Wheel build hooks - need libaegis built first
|
|
||||||
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
|
def build_wheel(wheel_directory, config_settings=None, metadata_directory=None):
|
||||||
_prepare()
|
"""Build wheel with libaegis built first."""
|
||||||
return build_meta.build_wheel(wheel_directory, config_settings, metadata_directory)
|
_build_libaegis()
|
||||||
|
return _orig.build_wheel(wheel_directory, config_settings, metadata_directory)
|
||||||
|
|
||||||
|
|
||||||
def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
|
def build_editable(wheel_directory, config_settings=None, metadata_directory=None):
|
||||||
_prepare()
|
"""Build editable install with libaegis built first."""
|
||||||
return build_meta.build_editable(
|
_build_libaegis()
|
||||||
wheel_directory, config_settings, metadata_directory
|
return _orig.build_editable(wheel_directory, config_settings, metadata_directory)
|
||||||
)
|
|
||||||
|
|||||||
Executable → Regular
+4
-35
@@ -1,4 +1,3 @@
|
|||||||
#!/usr/bin/env -S uv run
|
|
||||||
"""Generate CFFI cdef and Python modules from libaegis C sources."""
|
"""Generate CFFI cdef and Python modules from libaegis C sources."""
|
||||||
|
|
||||||
import pathlib
|
import pathlib
|
||||||
@@ -268,34 +267,17 @@ def generate_python_modules(
|
|||||||
if dst.exists() and dst.read_text(encoding="utf-8") == new_content:
|
if dst.exists() and dst.read_text(encoding="utf-8") == new_content:
|
||||||
unchanged.append(dst)
|
unchanged.append(dst)
|
||||||
else:
|
else:
|
||||||
dst.write_bytes(new_content.encode())
|
dst.write_text(new_content, encoding="utf-8")
|
||||||
updated.append(dst)
|
updated.append(dst)
|
||||||
|
|
||||||
return updated, unchanged
|
return updated, unchanged
|
||||||
|
|
||||||
|
|
||||||
def generate_ciphers_module(constants: Dict[str, Dict[str, int]]) -> str:
|
|
||||||
labels = [algo_label(variant) for variant in constants]
|
|
||||||
literal_items = ", ".join(f'"{label}"' for label in labels)
|
|
||||||
lines = [
|
|
||||||
"# This file is generated by tools/generate.py. Do not edit.",
|
|
||||||
"from typing import Literal",
|
|
||||||
"",
|
|
||||||
f"CipherName = Literal[{literal_items}]",
|
|
||||||
"",
|
|
||||||
"CIPHERS: dict[CipherName, str] = {",
|
|
||||||
]
|
|
||||||
for variant in constants:
|
|
||||||
lines.append(f' "{algo_label(variant)}": "{variant}",')
|
|
||||||
lines.append("}")
|
|
||||||
return "\n".join(lines) + "\n"
|
|
||||||
|
|
||||||
|
|
||||||
def main() -> int:
|
def main() -> int:
|
||||||
root = pathlib.Path(__file__).parent.parent
|
root = pathlib.Path(__file__).parent.parent
|
||||||
libaegis_src_dir = root / "libaegis" / "src"
|
libaegis_src_dir = root / "libaegis" / "src"
|
||||||
include_dir = libaegis_src_dir / "include"
|
include_dir = libaegis_src_dir / "include"
|
||||||
pyaegis_dir = root / "src" / "aeg"
|
pyaegis_dir = root / "pyaegis"
|
||||||
|
|
||||||
if not include_dir.exists():
|
if not include_dir.exists():
|
||||||
print(f"Include directory not found: {include_dir}", file=sys.stderr)
|
print(f"Include directory not found: {include_dir}", file=sys.stderr)
|
||||||
@@ -319,23 +301,10 @@ def main() -> int:
|
|||||||
if cdef_path.exists() and cdef_path.read_text(encoding="utf-8") == cdef_content:
|
if cdef_path.exists() and cdef_path.read_text(encoding="utf-8") == cdef_content:
|
||||||
print(f" - No changes to {cdef_path}", file=sys.stderr)
|
print(f" - No changes to {cdef_path}", file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
cdef_path.write_bytes(cdef_content.encode())
|
cdef_path.write_text(cdef_content, encoding="utf-8")
|
||||||
print(f" - Updated {cdef_path}", file=sys.stderr)
|
print(f" - Updated {cdef_path}", file=sys.stderr)
|
||||||
|
|
||||||
print("Step 3: Generating _ciphers.py...", file=sys.stderr)
|
print("Step 3: Generating Python modules...", file=sys.stderr)
|
||||||
ciphers_path = pyaegis_dir / "_ciphers.py"
|
|
||||||
ciphers_content = generate_ciphers_module(constants)
|
|
||||||
|
|
||||||
if (
|
|
||||||
ciphers_path.exists()
|
|
||||||
and ciphers_path.read_text(encoding="utf-8") == ciphers_content
|
|
||||||
):
|
|
||||||
print(f" - No changes to {ciphers_path.name}", file=sys.stderr)
|
|
||||||
else:
|
|
||||||
ciphers_path.write_bytes(ciphers_content.encode())
|
|
||||||
print(f" - Updated {ciphers_path.name}", file=sys.stderr)
|
|
||||||
|
|
||||||
print("Step 4: Generating Python modules...", file=sys.stderr)
|
|
||||||
try:
|
try:
|
||||||
updated, unchanged = generate_python_modules(
|
updated, unchanged = generate_python_modules(
|
||||||
pyaegis_dir / "aegis256x4.py", pyaegis_dir, constants
|
pyaegis_dir / "aegis256x4.py", pyaegis_dir, constants
|
||||||
|
|||||||
@@ -1,484 +0,0 @@
|
|||||||
#!/usr/bin/env -S uv run
|
|
||||||
"""Build wheels for all supported Python versions using uv."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import platform
|
|
||||||
import shutil
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
from packaging.version import Version
|
|
||||||
|
|
||||||
# Import generate module from same directory
|
|
||||||
sys.path.insert(0, str(Path(__file__).parent))
|
|
||||||
import generate
|
|
||||||
|
|
||||||
# Minimum macOS deployment target for compatibility
|
|
||||||
MACOS_DEPLOYMENT_TARGET = "11.0"
|
|
||||||
|
|
||||||
# ABI3 wheel: built once, works for all GIL-enabled Python versions
|
|
||||||
# We use a recent Python to build since it doesn't affect the wheel compatibility
|
|
||||||
ABI3_BUILD_VERSION = "3.14+gil"
|
|
||||||
|
|
||||||
# All GIL-enabled Python versions covered by the ABI3 wheel
|
|
||||||
ABI3_COVERED_VERSIONS = [
|
|
||||||
"3.10",
|
|
||||||
"3.11",
|
|
||||||
"3.12",
|
|
||||||
"3.13+gil",
|
|
||||||
"3.14+gil",
|
|
||||||
"3.15+gil",
|
|
||||||
]
|
|
||||||
|
|
||||||
# Non-ABI3 wheels: each needs its own build (free-threaded and PyPy)
|
|
||||||
NON_ABI3_VERSIONS = [
|
|
||||||
"3.14t",
|
|
||||||
"3.15t",
|
|
||||||
"pypy3.10",
|
|
||||||
"pypy3.11",
|
|
||||||
]
|
|
||||||
|
|
||||||
# All versions for testing and benchmarking
|
|
||||||
ALL_PYTHON_VERSIONS = ABI3_COVERED_VERSIONS + NON_ABI3_VERSIONS
|
|
||||||
|
|
||||||
|
|
||||||
def get_version_from_scm():
|
|
||||||
"""Get version from setuptools-scm (git tags)."""
|
|
||||||
try:
|
|
||||||
result = subprocess.run(
|
|
||||||
["uv", "run", "-m", "setuptools_scm"],
|
|
||||||
capture_output=True,
|
|
||||||
text=True,
|
|
||||||
check=True,
|
|
||||||
cwd=Path(__file__).parent.parent,
|
|
||||||
)
|
|
||||||
return result.stdout.strip()
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"✗ Error getting version from setuptools-scm: {e}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
|
|
||||||
def is_release_version(version):
|
|
||||||
"""Check if version is a clean release (no dev/post/local identifiers)."""
|
|
||||||
# A release version is just x.y.z with optional alpha/beta/rc suffixes
|
|
||||||
# No +local or .devN or .postN
|
|
||||||
if not version:
|
|
||||||
return False
|
|
||||||
return not any(marker in version for marker in ["+", ".dev", ".post"])
|
|
||||||
|
|
||||||
|
|
||||||
def get_next_version(current_version):
|
|
||||||
"""Get the next release version from a dev version."""
|
|
||||||
# Parse base version (strips dev/local parts)
|
|
||||||
try:
|
|
||||||
v = Version(current_version)
|
|
||||||
return f"{v.major}.{v.minor}.{v.micro}"
|
|
||||||
except Exception:
|
|
||||||
return current_version
|
|
||||||
|
|
||||||
|
|
||||||
def is_working_copy_clean():
|
|
||||||
"""Check if git working copy is clean."""
|
|
||||||
result = subprocess.run(
|
|
||||||
["git", "status", "--porcelain"], capture_output=True, text=True
|
|
||||||
)
|
|
||||||
return result.returncode == 0 and not result.stdout.strip()
|
|
||||||
|
|
||||||
|
|
||||||
def make_release_message(version):
|
|
||||||
"""Generate message for making a release."""
|
|
||||||
next_version = get_next_version(version)
|
|
||||||
is_clean = is_working_copy_clean()
|
|
||||||
|
|
||||||
msg = "\n⚠️ This is not a clean release version; upload to PyPI skipped.\n\n"
|
|
||||||
msg += f"To create a release (e.g. {next_version}) and upload to PyPI:\n"
|
|
||||||
|
|
||||||
if not is_clean:
|
|
||||||
msg += " 1. Add and commit changes on the working copy\n"
|
|
||||||
msg += f" 2. Tag the commit: git tag v{next_version}\n"
|
|
||||||
msg += " 3. Run this script again\n"
|
|
||||||
msg += f" 4. Push the tag: git push origin v{next_version}\n"
|
|
||||||
else:
|
|
||||||
msg += f" 1. Tag the current commit: git tag v{next_version}\n"
|
|
||||||
msg += " 2. Run this script again\n"
|
|
||||||
msg += f" 3. Push the tag: git push origin v{next_version}\n"
|
|
||||||
|
|
||||||
msg += (
|
|
||||||
f"\nIf the build didn't work, delete the tag with git tag -d v{next_version}\n"
|
|
||||||
)
|
|
||||||
return msg
|
|
||||||
|
|
||||||
|
|
||||||
def run_command(cmd, description=None, env=None):
|
|
||||||
"""Run a command and handle errors. If description is None, only print the command."""
|
|
||||||
if description:
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print(f"{description}")
|
|
||||||
print(f"{'=' * 70}")
|
|
||||||
print(f">>> {' '.join(cmd)}")
|
|
||||||
try:
|
|
||||||
subprocess.run(cmd, check=True, env=env)
|
|
||||||
return True
|
|
||||||
except subprocess.CalledProcessError as e:
|
|
||||||
print(f"✗ Command failed with exit code {e.returncode}", file=sys.stderr)
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def get_build_env():
|
|
||||||
"""Get environment variables for building wheels."""
|
|
||||||
env = os.environ.copy()
|
|
||||||
if platform.system() == "Darwin":
|
|
||||||
env["MACOSX_DEPLOYMENT_TARGET"] = MACOS_DEPLOYMENT_TARGET
|
|
||||||
return env
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_line_endings(repo_root: Path):
|
|
||||||
"""Normalize all text files to LF line endings."""
|
|
||||||
# Patterns for files to normalize
|
|
||||||
patterns = [
|
|
||||||
"src/aeg/**/*.py",
|
|
||||||
"src/aeg/**/*.h",
|
|
||||||
"tests/**/*.py",
|
|
||||||
"tools/**/*.py",
|
|
||||||
"*.py",
|
|
||||||
"*.md",
|
|
||||||
"*.txt",
|
|
||||||
"*.toml",
|
|
||||||
"*.in",
|
|
||||||
]
|
|
||||||
for pattern in patterns:
|
|
||||||
for file_path in repo_root.glob(pattern):
|
|
||||||
if file_path.is_file():
|
|
||||||
content = file_path.read_bytes()
|
|
||||||
if b"\r\n" in content:
|
|
||||||
content = content.replace(b"\r\n", b"\n")
|
|
||||||
file_path.write_bytes(content)
|
|
||||||
|
|
||||||
|
|
||||||
def get_wheel_pattern(py_version: str, abi3: bool = False) -> str:
|
|
||||||
"""Get the glob pattern for finding a wheel file."""
|
|
||||||
if abi3:
|
|
||||||
# ABI3 wheels always use cp310-abi3 tag (minimum supported version)
|
|
||||||
# regardless of which Python version was used to build
|
|
||||||
return "aeg-*-cp310-abi3-*.whl"
|
|
||||||
elif py_version.startswith("pypy"):
|
|
||||||
# PyPy wheels use pp3XX format
|
|
||||||
return f"aeg-*-pp{py_version.replace('pypy', '').replace('.', '')}-*.whl"
|
|
||||||
elif py_version.endswith("t"):
|
|
||||||
# Free-threaded Python wheels use cpXXX-cpXXXt format (e.g., cp314-cp314t)
|
|
||||||
base_version = py_version.replace(".", "").replace("t", "")
|
|
||||||
return f"aeg-*-cp{base_version}-cp{base_version}t-*.whl"
|
|
||||||
else:
|
|
||||||
# Regular CPython wheels use cpXXX-cpXXX format
|
|
||||||
# Strip +gil suffix used to force non-free-threaded build
|
|
||||||
base_version = py_version.replace(".", "").replace("+gil", "")
|
|
||||||
return f"aeg-*-cp{base_version}-cp{base_version}-*.whl"
|
|
||||||
|
|
||||||
|
|
||||||
def build_abi3_wheel(dist_dir: Path, py_version: str) -> Path | None:
|
|
||||||
"""Build the ABI3 wheel using the specified Python version."""
|
|
||||||
cmd = ["uv", "build", "--python", py_version, "--wheel", "--quiet"]
|
|
||||||
|
|
||||||
if not run_command(cmd, env=get_build_env()):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Find the ABI3 wheel (always tagged cp310-abi3 regardless of build Python version)
|
|
||||||
wheel_pattern = get_wheel_pattern(py_version, abi3=True)
|
|
||||||
wheels = list(dist_dir.glob(wheel_pattern))
|
|
||||||
if not wheels:
|
|
||||||
print(f"✗ Could not find ABI3 wheel matching {wheel_pattern}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
wheel = wheels[0]
|
|
||||||
|
|
||||||
# Repair wheel with auditwheel for manylinux compatibility (Linux only)
|
|
||||||
if platform.system() == "Linux":
|
|
||||||
wheel = repair_wheel_linux(dist_dir, wheel, py_version, abi3=True)
|
|
||||||
if not wheel:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return wheel
|
|
||||||
|
|
||||||
|
|
||||||
def build_wheel_for_version(dist_dir: Path, py_version: str) -> Path | None:
|
|
||||||
"""Build a wheel for a specific Python version (non-ABI3)."""
|
|
||||||
cmd = ["uv", "build", "--python", py_version, "--wheel", "--quiet"]
|
|
||||||
|
|
||||||
if not run_command(cmd, env=get_build_env()):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Find the wheel for this version
|
|
||||||
wheel_pattern = get_wheel_pattern(py_version, abi3=False)
|
|
||||||
wheels = list(dist_dir.glob(wheel_pattern))
|
|
||||||
if not wheels:
|
|
||||||
print(f"✗ Could not find wheel for Python {py_version}", file=sys.stderr)
|
|
||||||
return None
|
|
||||||
|
|
||||||
wheel = wheels[0]
|
|
||||||
|
|
||||||
# Repair wheel with auditwheel for manylinux compatibility (Linux only)
|
|
||||||
if platform.system() == "Linux":
|
|
||||||
wheel = repair_wheel_linux(dist_dir, wheel, py_version, abi3=False)
|
|
||||||
if not wheel:
|
|
||||||
return None
|
|
||||||
|
|
||||||
return wheel
|
|
||||||
|
|
||||||
|
|
||||||
def repair_wheel_linux(
|
|
||||||
dist_dir: Path, wheel: Path, py_version: str, abi3: bool
|
|
||||||
) -> Path | None:
|
|
||||||
"""Repair a wheel with auditwheel for manylinux compatibility (Linux only)."""
|
|
||||||
repair_cmd = [
|
|
||||||
"uv",
|
|
||||||
"run",
|
|
||||||
"auditwheel",
|
|
||||||
"repair",
|
|
||||||
str(wheel),
|
|
||||||
"-w",
|
|
||||||
str(dist_dir),
|
|
||||||
]
|
|
||||||
if not run_command(repair_cmd):
|
|
||||||
return None
|
|
||||||
|
|
||||||
# Find the repaired wheel (it will have a different name)
|
|
||||||
wheel_pattern = get_wheel_pattern(py_version, abi3=abi3)
|
|
||||||
all_wheels = list(dist_dir.glob(wheel_pattern))
|
|
||||||
repaired_wheels = [w for w in all_wheels if "linux_x86_64" not in str(w)]
|
|
||||||
if not repaired_wheels:
|
|
||||||
print(
|
|
||||||
f"✗ Could not find repaired (manylinux) wheel for Python {py_version}",
|
|
||||||
file=sys.stderr,
|
|
||||||
)
|
|
||||||
return None
|
|
||||||
|
|
||||||
repaired_wheel = repaired_wheels[0]
|
|
||||||
|
|
||||||
# Remove the unrepaired linux_x86_64 wheels
|
|
||||||
for w in all_wheels:
|
|
||||||
if "linux_x86_64" in str(w):
|
|
||||||
w.unlink()
|
|
||||||
|
|
||||||
return repaired_wheel
|
|
||||||
|
|
||||||
|
|
||||||
def test_wheel(wheel: Path, py_version: str) -> bool:
|
|
||||||
"""Test a wheel with pytest."""
|
|
||||||
# --isolated: avoid .venv conflicts
|
|
||||||
# --no-project: don't build from source in current directory, use the wheel
|
|
||||||
# --refresh-package: force uv to not use cached old versions
|
|
||||||
test_cmd = [
|
|
||||||
"uv",
|
|
||||||
"run",
|
|
||||||
"--isolated",
|
|
||||||
"--no-project",
|
|
||||||
"--refresh-package",
|
|
||||||
"aeg",
|
|
||||||
"--python",
|
|
||||||
py_version,
|
|
||||||
"--with",
|
|
||||||
str(wheel),
|
|
||||||
"--with",
|
|
||||||
"pytest",
|
|
||||||
"pytest",
|
|
||||||
"tests/",
|
|
||||||
]
|
|
||||||
return run_command(test_cmd)
|
|
||||||
|
|
||||||
|
|
||||||
def run_benchmark(wheel: Path, py_version: str) -> bool:
|
|
||||||
"""Run benchmark for a wheel."""
|
|
||||||
# --isolated: avoid .venv conflicts
|
|
||||||
# --no-project: don't build from source in current directory, use the wheel
|
|
||||||
# --refresh-package: force uv to not use cached old versions
|
|
||||||
bench_cmd = [
|
|
||||||
"uv",
|
|
||||||
"run",
|
|
||||||
"--isolated",
|
|
||||||
"--no-project",
|
|
||||||
"--refresh-package",
|
|
||||||
"aeg",
|
|
||||||
"--python",
|
|
||||||
py_version,
|
|
||||||
"--with",
|
|
||||||
str(wheel),
|
|
||||||
"-m",
|
|
||||||
"aeg.benchmark",
|
|
||||||
]
|
|
||||||
return run_command(bench_cmd)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Build wheels for all supported Python versions."""
|
|
||||||
repo_root = Path(__file__).parent.parent
|
|
||||||
dist_dir = repo_root / "dist"
|
|
||||||
|
|
||||||
# Generate CFFI definitions and Python modules
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print("Code generation from C headers (tools/generate.py)")
|
|
||||||
print(f"{'=' * 70}")
|
|
||||||
if generate.main() != 0:
|
|
||||||
print("✗ Code generation failed", file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Run ruff to check and fix any issues
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print("Linting and formatting")
|
|
||||||
print(f"{'=' * 70}")
|
|
||||||
if not run_command(["uv", "run", "ruff", "check", "--fix", "."]):
|
|
||||||
print("✗ Ruff check failed", file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Run ruff format
|
|
||||||
if not run_command(["uv", "run", "ruff", "format", "."]):
|
|
||||||
print("✗ Ruff format failed", file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# Normalize all line endings to LF (important for consistent builds)
|
|
||||||
normalize_line_endings(repo_root)
|
|
||||||
|
|
||||||
# Get version from git repo
|
|
||||||
version = get_version_from_scm()
|
|
||||||
if not version:
|
|
||||||
return 1
|
|
||||||
is_release = is_release_version(version)
|
|
||||||
|
|
||||||
# Main header for the packaging process
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print(
|
|
||||||
f"Packaging aeg-{version}"
|
|
||||||
+ (" for release" if is_release else " (not release)")
|
|
||||||
)
|
|
||||||
print(f"Building: 1 ABI3 wheel (for Python {', '.join(ABI3_COVERED_VERSIONS)})")
|
|
||||||
print(
|
|
||||||
f" + {len(NON_ABI3_VERSIONS)} non-ABI3 wheels ({', '.join(NON_ABI3_VERSIONS)})"
|
|
||||||
)
|
|
||||||
print(f"Testing/benchmarking: {len(ALL_PYTHON_VERSIONS)} Python versions")
|
|
||||||
print(f"Output directory: {dist_dir}", end=" ")
|
|
||||||
|
|
||||||
# Clean dist directory
|
|
||||||
if dist_dir.exists():
|
|
||||||
print("(wiped)")
|
|
||||||
shutil.rmtree(dist_dir)
|
|
||||||
else:
|
|
||||||
print("(created)")
|
|
||||||
|
|
||||||
# Clean build directory to remove stale CFFI-generated C code and .so files
|
|
||||||
build_dir = repo_root / "build"
|
|
||||||
if build_dir.exists():
|
|
||||||
print(f"Cleaning build directory: {build_dir}")
|
|
||||||
shutil.rmtree(build_dir)
|
|
||||||
|
|
||||||
# Build distributions
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print("Building distributions")
|
|
||||||
print(f"{'=' * 70}")
|
|
||||||
|
|
||||||
# Build source distribution first
|
|
||||||
if not run_command(["uv", "build", "--sdist", "--quiet"], env=get_build_env()):
|
|
||||||
print("✗ Source distribution build failed", file=sys.stderr)
|
|
||||||
return 1
|
|
||||||
|
|
||||||
failed_builds = []
|
|
||||||
failed_tests = []
|
|
||||||
successful_wheels = []
|
|
||||||
wheel_for_version = {} # Map Python version to wheel path
|
|
||||||
|
|
||||||
# Build ABI3 wheel (once, works for all GIL-enabled versions)
|
|
||||||
abi3_wheel = build_abi3_wheel(dist_dir, ABI3_BUILD_VERSION)
|
|
||||||
if abi3_wheel:
|
|
||||||
successful_wheels.append(abi3_wheel)
|
|
||||||
# This wheel works for all ABI3-covered versions
|
|
||||||
for py_version in ABI3_COVERED_VERSIONS:
|
|
||||||
wheel_for_version[py_version] = abi3_wheel
|
|
||||||
else:
|
|
||||||
failed_builds.append(f"abi3 (built with {ABI3_BUILD_VERSION})")
|
|
||||||
|
|
||||||
# Build non-ABI3 wheels (free-threaded and PyPy)
|
|
||||||
for py_version in NON_ABI3_VERSIONS:
|
|
||||||
wheel = build_wheel_for_version(dist_dir, py_version)
|
|
||||||
if wheel:
|
|
||||||
successful_wheels.append(wheel)
|
|
||||||
wheel_for_version[py_version] = wheel
|
|
||||||
else:
|
|
||||||
failed_builds.append(py_version)
|
|
||||||
|
|
||||||
# Test and benchmark each Python version with its appropriate wheel
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print("Testing and benchmarking")
|
|
||||||
print(f"{'=' * 70}")
|
|
||||||
|
|
||||||
for py_version in ALL_PYTHON_VERSIONS:
|
|
||||||
wheel = wheel_for_version.get(py_version)
|
|
||||||
if not wheel:
|
|
||||||
# No wheel available for this version (build failed)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Test the wheel with pytest
|
|
||||||
if not test_wheel(wheel, py_version):
|
|
||||||
failed_tests.append(py_version)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Run benchmark
|
|
||||||
if not run_benchmark(wheel, py_version):
|
|
||||||
failed_tests.append(py_version)
|
|
||||||
continue
|
|
||||||
|
|
||||||
# Summary
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print("BUILD SUMMARY")
|
|
||||||
print(f"{'=' * 70}")
|
|
||||||
print(
|
|
||||||
f"Successful builds: sdist and {len(successful_wheels)} wheels "
|
|
||||||
f"(1 abi3 + {len(NON_ABI3_VERSIONS)} non-abi3)"
|
|
||||||
)
|
|
||||||
print(
|
|
||||||
f"Tests/benchmarks passed: {len(ALL_PYTHON_VERSIONS) - len(failed_tests) - len(failed_builds)}/{len(ALL_PYTHON_VERSIONS)} Python versions"
|
|
||||||
)
|
|
||||||
|
|
||||||
if failed_builds:
|
|
||||||
print(f"\nFailed builds: {len(failed_builds)}")
|
|
||||||
for failed_version in failed_builds:
|
|
||||||
print(f" ✗ {failed_version}")
|
|
||||||
|
|
||||||
if failed_tests:
|
|
||||||
print(f"\nFailed tests/benchmarks: {len(failed_tests)}")
|
|
||||||
for failed_version in failed_tests:
|
|
||||||
print(f" ✗ Python {failed_version}")
|
|
||||||
|
|
||||||
if not successful_wheels:
|
|
||||||
print("\n✗ No successful wheels to upload")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
# List files to upload
|
|
||||||
sdist = list(dist_dir.glob("*.tar.gz"))
|
|
||||||
upload_files = sdist + successful_wheels
|
|
||||||
|
|
||||||
for file in upload_files:
|
|
||||||
print(f" - {file.name}")
|
|
||||||
|
|
||||||
# Only upload if this is a clean release version
|
|
||||||
if not is_release:
|
|
||||||
print(make_release_message(version))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
# Upload with twine
|
|
||||||
upload_cmd = ["uvx", "twine", "upload"] + [str(f) for f in upload_files]
|
|
||||||
if not run_command(upload_cmd, "Uploading to PyPI with twine"):
|
|
||||||
print("\n✗ Upload failed")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
print(f"\n{'=' * 70}")
|
|
||||||
print("All builds and upload completed successfully!")
|
|
||||||
print(f"{'=' * 70}")
|
|
||||||
print()
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
try:
|
|
||||||
sys.exit(main())
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
sys.exit(1)
|
|
||||||
Reference in New Issue
Block a user