diff --git a/README.md b/README.md
index 6c96787..481faee 100644
--- a/README.md
+++ b/README.md
@@ -193,6 +193,47 @@ Run the built-in benchmark to see which variant is fastest on your machine:
python -m pyaegis.benchmark
```
+Benchmarks of the Python module and the C library 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.
+
+
+
+|
+```bash
+$ python -m pyaegis.benchmark
+AEGIS-256 107666.56 Mb/s
+AEGIS-256X2 191314.53 Mb/s
+AEGIS-256X4 211537.44 Mb/s
+AEGIS-128L 159074.08 Mb/s
+AEGIS-128X2 307332.53 Mb/s
+AEGIS-128X4 230106.70 Mb/s
+AEGIS-128L MAC 206082.24 Mb/s
+AEGIS-128X2 MAC 366401.20 Mb/s
+AEGIS-128X4 MAC 375011.51 Mb/s
+AEGIS-256 MAC 110187.03 Mb/s
+AEGIS-256X2 MAC 210063.51 Mb/s
+AEGIS-256X4 MAC 347406.96 Mb/s
+```
+ |
+
+```bash
+$ ./libaegis/zig-out/bin/benchmark
+AEGIS-256 107820.86 Mb/s
+AEGIS-256X2 205025.57 Mb/s
+AEGIS-256X4 223361.81 Mb/s
+AEGIS-128L 187530.77 Mb/s
+AEGIS-128X2 354003.14 Mb/s
+AEGIS-128X4 218596.59 Mb/s
+AEGIS-128L MAC 224276.49 Mb/s
+AEGIS-128X2 MAC 417741.65 Mb/s
+AEGIS-128X4 MAC 410454.05 Mb/s
+AEGIS-256 MAC 116776.62 Mb/s
+AEGIS-256X2 MAC 224150.04 Mb/s
+AEGIS-256X4 MAC 392088.05 Mb/s
+```
+ |
+
+
+
## Errors
- Authentication failures raise ValueError.
diff --git a/pyaegis/aegis128l.py b/pyaegis/aegis128l.py
index f02986b..a32adbd 100644
--- a/pyaegis/aegis128l.py
+++ b/pyaegis/aegis128l.py
@@ -7,7 +7,7 @@ import secrets
from ._loader import ffi
from ._loader import lib as _lib
-from .util import Buffer, new_aligned_struct
+from .util import Buffer, new_aligned_struct, nonce_inc_inplace
# Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis128l_keybytes()
@@ -19,14 +19,14 @@ ALIGNMENT = 32
RATE = 32
-def random_key() -> bytes:
+def random_key() -> bytearray:
"""Generate a random key using cryptographically secure random bytes."""
- return secrets.token_bytes(KEYBYTES)
+ return bytearray(secrets.token_bytes(KEYBYTES))
-def random_nonce() -> bytes:
+def random_nonce() -> bytearray:
"""Generate a random nonce using cryptographically secure random bytes."""
- return secrets.token_bytes(NPUBBYTES)
+ return bytearray(secrets.token_bytes(NPUBBYTES))
def _ptr(buf):
@@ -802,6 +802,10 @@ __all__ = [
"TAILBYTES_MAX",
"ALIGNMENT",
"RATE",
+ # utility functions
+ "random_key",
+ "random_nonce",
+ "nonce_inc_inplace",
# one-shot functions
"encrypt_detached",
"decrypt_detached",
diff --git a/pyaegis/aegis128x2.py b/pyaegis/aegis128x2.py
index 4f1aa7c..8d9b9b1 100644
--- a/pyaegis/aegis128x2.py
+++ b/pyaegis/aegis128x2.py
@@ -7,7 +7,7 @@ import secrets
from ._loader import ffi
from ._loader import lib as _lib
-from .util import Buffer, new_aligned_struct
+from .util import Buffer, new_aligned_struct, nonce_inc_inplace
# Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis128x2_keybytes()
@@ -19,14 +19,14 @@ ALIGNMENT = 64
RATE = 64
-def random_key() -> bytes:
+def random_key() -> bytearray:
"""Generate a random key using cryptographically secure random bytes."""
- return secrets.token_bytes(KEYBYTES)
+ return bytearray(secrets.token_bytes(KEYBYTES))
-def random_nonce() -> bytes:
+def random_nonce() -> bytearray:
"""Generate a random nonce using cryptographically secure random bytes."""
- return secrets.token_bytes(NPUBBYTES)
+ return bytearray(secrets.token_bytes(NPUBBYTES))
def _ptr(buf):
@@ -802,6 +802,10 @@ __all__ = [
"TAILBYTES_MAX",
"ALIGNMENT",
"RATE",
+ # utility functions
+ "random_key",
+ "random_nonce",
+ "nonce_inc_inplace",
# one-shot functions
"encrypt_detached",
"decrypt_detached",
diff --git a/pyaegis/aegis128x4.py b/pyaegis/aegis128x4.py
index d2a8b6c..4bcb31f 100644
--- a/pyaegis/aegis128x4.py
+++ b/pyaegis/aegis128x4.py
@@ -7,7 +7,7 @@ import secrets
from ._loader import ffi
from ._loader import lib as _lib
-from .util import Buffer, new_aligned_struct
+from .util import Buffer, new_aligned_struct, nonce_inc_inplace
# Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis128x4_keybytes()
@@ -19,14 +19,14 @@ ALIGNMENT = 64
RATE = 128
-def random_key() -> bytes:
+def random_key() -> bytearray:
"""Generate a random key using cryptographically secure random bytes."""
- return secrets.token_bytes(KEYBYTES)
+ return bytearray(secrets.token_bytes(KEYBYTES))
-def random_nonce() -> bytes:
+def random_nonce() -> bytearray:
"""Generate a random nonce using cryptographically secure random bytes."""
- return secrets.token_bytes(NPUBBYTES)
+ return bytearray(secrets.token_bytes(NPUBBYTES))
def _ptr(buf):
@@ -802,6 +802,10 @@ __all__ = [
"TAILBYTES_MAX",
"ALIGNMENT",
"RATE",
+ # utility functions
+ "random_key",
+ "random_nonce",
+ "nonce_inc_inplace",
# one-shot functions
"encrypt_detached",
"decrypt_detached",
diff --git a/pyaegis/aegis256.py b/pyaegis/aegis256.py
index 22675b7..989487c 100644
--- a/pyaegis/aegis256.py
+++ b/pyaegis/aegis256.py
@@ -7,7 +7,7 @@ import secrets
from ._loader import ffi
from ._loader import lib as _lib
-from .util import Buffer, new_aligned_struct
+from .util import Buffer, new_aligned_struct, nonce_inc_inplace
# Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis256_keybytes()
@@ -19,14 +19,14 @@ ALIGNMENT = 16
RATE = 16
-def random_key() -> bytes:
+def random_key() -> bytearray:
"""Generate a random key using cryptographically secure random bytes."""
- return secrets.token_bytes(KEYBYTES)
+ return bytearray(secrets.token_bytes(KEYBYTES))
-def random_nonce() -> bytes:
+def random_nonce() -> bytearray:
"""Generate a random nonce using cryptographically secure random bytes."""
- return secrets.token_bytes(NPUBBYTES)
+ return bytearray(secrets.token_bytes(NPUBBYTES))
def _ptr(buf):
@@ -802,6 +802,10 @@ __all__ = [
"TAILBYTES_MAX",
"ALIGNMENT",
"RATE",
+ # utility functions
+ "random_key",
+ "random_nonce",
+ "nonce_inc_inplace",
# one-shot functions
"encrypt_detached",
"decrypt_detached",
diff --git a/pyaegis/aegis256x2.py b/pyaegis/aegis256x2.py
index 6e6ac9c..24a1d62 100644
--- a/pyaegis/aegis256x2.py
+++ b/pyaegis/aegis256x2.py
@@ -7,7 +7,7 @@ import secrets
from ._loader import ffi
from ._loader import lib as _lib
-from .util import Buffer, new_aligned_struct
+from .util import Buffer, new_aligned_struct, nonce_inc_inplace
# Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis256x2_keybytes()
@@ -19,14 +19,14 @@ ALIGNMENT = 32
RATE = 32
-def random_key() -> bytes:
+def random_key() -> bytearray:
"""Generate a random key using cryptographically secure random bytes."""
- return secrets.token_bytes(KEYBYTES)
+ return bytearray(secrets.token_bytes(KEYBYTES))
-def random_nonce() -> bytes:
+def random_nonce() -> bytearray:
"""Generate a random nonce using cryptographically secure random bytes."""
- return secrets.token_bytes(NPUBBYTES)
+ return bytearray(secrets.token_bytes(NPUBBYTES))
def _ptr(buf):
@@ -802,6 +802,10 @@ __all__ = [
"TAILBYTES_MAX",
"ALIGNMENT",
"RATE",
+ # utility functions
+ "random_key",
+ "random_nonce",
+ "nonce_inc_inplace",
# one-shot functions
"encrypt_detached",
"decrypt_detached",
diff --git a/pyaegis/aegis256x4.py b/pyaegis/aegis256x4.py
index 774db88..20b2525 100644
--- a/pyaegis/aegis256x4.py
+++ b/pyaegis/aegis256x4.py
@@ -7,7 +7,7 @@ import secrets
from ._loader import ffi
from ._loader import lib as _lib
-from .util import Buffer, new_aligned_struct
+from .util import Buffer, new_aligned_struct, nonce_inc_inplace
# Constants exposed as functions in C; mirror them as integers at module import time
KEYBYTES = _lib.aegis256x4_keybytes()
@@ -19,14 +19,14 @@ ALIGNMENT = 64
RATE = 64
-def random_key() -> bytes:
+def random_key() -> bytearray:
"""Generate a random key using cryptographically secure random bytes."""
- return secrets.token_bytes(KEYBYTES)
+ return bytearray(secrets.token_bytes(KEYBYTES))
-def random_nonce() -> bytes:
+def random_nonce() -> bytearray:
"""Generate a random nonce using cryptographically secure random bytes."""
- return secrets.token_bytes(NPUBBYTES)
+ return bytearray(secrets.token_bytes(NPUBBYTES))
def _ptr(buf):
@@ -802,6 +802,10 @@ __all__ = [
"TAILBYTES_MAX",
"ALIGNMENT",
"RATE",
+ # utility functions
+ "random_key",
+ "random_nonce",
+ "nonce_inc_inplace",
# one-shot functions
"encrypt_detached",
"decrypt_detached",
diff --git a/pyaegis/util.py b/pyaegis/util.py
index 41ad4f0..8523929 100644
--- a/pyaegis/util.py
+++ b/pyaegis/util.py
@@ -10,7 +10,7 @@ from typing import Protocol
from ._loader import ffi
-__all__ = ["new_aligned_struct", "aligned_address", "Buffer"]
+__all__ = ["new_aligned_struct", "aligned_address", "Buffer", "nonce_increment"]
try:
from collections.abc import Buffer as _Buffer
@@ -45,3 +45,19 @@ def new_aligned_struct(ctype: str, alignment: int) -> tuple[object, object]:
aligned_uc = ffi.addressof(base, offset)
ptr = ffi.cast(f"{ctype} *", aligned_uc)
return ptr, base
+
+
+def nonce_increment(nonce: Buffer) -> None:
+ """Increment the nonce in place using little-endian byte order.
+
+ Useful for generating unique nonces for each consecutive message.
+
+ Args:
+ nonce: The nonce buffer to increment (modified in place).
+ """
+ n = memoryview(nonce)
+ for i in range(len(n)):
+ if n[i] < 255:
+ n[i] += 1
+ return
+ n[i] = 0