about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/nacl
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/nacl
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
Diffstat (limited to '.venv/lib/python3.12/site-packages/nacl')
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/__init__.py39
-rwxr-xr-x.venv/lib/python3.12/site-packages/nacl/_sodium.abi3.sobin0 -> 2740136 bytes
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/__init__.py451
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_aead.py559
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_box.py324
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_core.py412
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_generichash.py281
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_hash.py63
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_kx.py200
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_pwhash.py600
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_scalarmult.py240
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretbox.py86
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretstream.py357
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_shorthash.py81
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/crypto_sign.py327
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/randombytes.py51
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/sodium_core.py33
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/bindings/utils.py141
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/encoding.py105
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/exceptions.py88
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/hash.py182
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/hashlib.py143
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/public.py423
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/pwhash/__init__.py75
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/pwhash/_argon2.py49
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/pwhash/argon2i.py132
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/pwhash/argon2id.py135
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/pwhash/scrypt.py211
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/py.typed0
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/secret.py305
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/signing.py250
-rw-r--r--.venv/lib/python3.12/site-packages/nacl/utils.py88
32 files changed, 6431 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/nacl/__init__.py b/.venv/lib/python3.12/site-packages/nacl/__init__.py
new file mode 100644
index 00000000..cc8b7ea7
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/__init__.py
@@ -0,0 +1,39 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+__all__ = [
+    "__title__",
+    "__summary__",
+    "__uri__",
+    "__version__",
+    "__author__",
+    "__email__",
+    "__license__",
+    "__copyright__",
+]
+
+__title__ = "PyNaCl"
+__summary__ = (
+    "Python binding to the Networking and Cryptography (NaCl) library"
+)
+__uri__ = "https://github.com/pyca/pynacl/"
+
+__version__ = "1.5.0"
+
+__author__ = "The PyNaCl developers"
+__email__ = "cryptography-dev@python.org"
+
+__license__ = "Apache License 2.0"
+__copyright__ = "Copyright 2013-2018 {}".format(__author__)
diff --git a/.venv/lib/python3.12/site-packages/nacl/_sodium.abi3.so b/.venv/lib/python3.12/site-packages/nacl/_sodium.abi3.so
new file mode 100755
index 00000000..448331e8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/_sodium.abi3.so
Binary files differdiff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/__init__.py b/.venv/lib/python3.12/site-packages/nacl/bindings/__init__.py
new file mode 100644
index 00000000..1e3b10e8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/__init__.py
@@ -0,0 +1,451 @@
+# Copyright 2013-2019 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from nacl.bindings.crypto_aead import (
+    crypto_aead_chacha20poly1305_ABYTES,
+    crypto_aead_chacha20poly1305_KEYBYTES,
+    crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX,
+    crypto_aead_chacha20poly1305_NPUBBYTES,
+    crypto_aead_chacha20poly1305_NSECBYTES,
+    crypto_aead_chacha20poly1305_decrypt,
+    crypto_aead_chacha20poly1305_encrypt,
+    crypto_aead_chacha20poly1305_ietf_ABYTES,
+    crypto_aead_chacha20poly1305_ietf_KEYBYTES,
+    crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX,
+    crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
+    crypto_aead_chacha20poly1305_ietf_NSECBYTES,
+    crypto_aead_chacha20poly1305_ietf_decrypt,
+    crypto_aead_chacha20poly1305_ietf_encrypt,
+    crypto_aead_xchacha20poly1305_ietf_ABYTES,
+    crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
+    crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX,
+    crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
+    crypto_aead_xchacha20poly1305_ietf_NSECBYTES,
+    crypto_aead_xchacha20poly1305_ietf_decrypt,
+    crypto_aead_xchacha20poly1305_ietf_encrypt,
+)
+from nacl.bindings.crypto_box import (
+    crypto_box,
+    crypto_box_BEFORENMBYTES,
+    crypto_box_BOXZEROBYTES,
+    crypto_box_NONCEBYTES,
+    crypto_box_PUBLICKEYBYTES,
+    crypto_box_SEALBYTES,
+    crypto_box_SECRETKEYBYTES,
+    crypto_box_SEEDBYTES,
+    crypto_box_ZEROBYTES,
+    crypto_box_afternm,
+    crypto_box_beforenm,
+    crypto_box_keypair,
+    crypto_box_open,
+    crypto_box_open_afternm,
+    crypto_box_seal,
+    crypto_box_seal_open,
+    crypto_box_seed_keypair,
+)
+from nacl.bindings.crypto_core import (
+    crypto_core_ed25519_BYTES,
+    crypto_core_ed25519_NONREDUCEDSCALARBYTES,
+    crypto_core_ed25519_SCALARBYTES,
+    crypto_core_ed25519_add,
+    crypto_core_ed25519_is_valid_point,
+    crypto_core_ed25519_scalar_add,
+    crypto_core_ed25519_scalar_complement,
+    crypto_core_ed25519_scalar_invert,
+    crypto_core_ed25519_scalar_mul,
+    crypto_core_ed25519_scalar_negate,
+    crypto_core_ed25519_scalar_reduce,
+    crypto_core_ed25519_scalar_sub,
+    crypto_core_ed25519_sub,
+    has_crypto_core_ed25519,
+)
+from nacl.bindings.crypto_generichash import (
+    crypto_generichash_BYTES,
+    crypto_generichash_BYTES_MAX,
+    crypto_generichash_BYTES_MIN,
+    crypto_generichash_KEYBYTES,
+    crypto_generichash_KEYBYTES_MAX,
+    crypto_generichash_KEYBYTES_MIN,
+    crypto_generichash_PERSONALBYTES,
+    crypto_generichash_SALTBYTES,
+    crypto_generichash_STATEBYTES,
+    generichash_blake2b_final as crypto_generichash_blake2b_final,
+    generichash_blake2b_init as crypto_generichash_blake2b_init,
+    generichash_blake2b_salt_personal as crypto_generichash_blake2b_salt_personal,
+    generichash_blake2b_update as crypto_generichash_blake2b_update,
+)
+from nacl.bindings.crypto_hash import (
+    crypto_hash,
+    crypto_hash_BYTES,
+    crypto_hash_sha256,
+    crypto_hash_sha256_BYTES,
+    crypto_hash_sha512,
+    crypto_hash_sha512_BYTES,
+)
+from nacl.bindings.crypto_kx import (
+    crypto_kx_PUBLIC_KEY_BYTES,
+    crypto_kx_SECRET_KEY_BYTES,
+    crypto_kx_SEED_BYTES,
+    crypto_kx_SESSION_KEY_BYTES,
+    crypto_kx_client_session_keys,
+    crypto_kx_keypair,
+    crypto_kx_seed_keypair,
+    crypto_kx_server_session_keys,
+)
+from nacl.bindings.crypto_pwhash import (
+    crypto_pwhash_ALG_ARGON2I13,
+    crypto_pwhash_ALG_ARGON2ID13,
+    crypto_pwhash_ALG_DEFAULT,
+    crypto_pwhash_BYTES_MAX,
+    crypto_pwhash_BYTES_MIN,
+    crypto_pwhash_PASSWD_MAX,
+    crypto_pwhash_PASSWD_MIN,
+    crypto_pwhash_SALTBYTES,
+    crypto_pwhash_STRBYTES,
+    crypto_pwhash_alg,
+    crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE,
+    crypto_pwhash_argon2i_MEMLIMIT_MAX,
+    crypto_pwhash_argon2i_MEMLIMIT_MIN,
+    crypto_pwhash_argon2i_MEMLIMIT_MODERATE,
+    crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE,
+    crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE,
+    crypto_pwhash_argon2i_OPSLIMIT_MAX,
+    crypto_pwhash_argon2i_OPSLIMIT_MIN,
+    crypto_pwhash_argon2i_OPSLIMIT_MODERATE,
+    crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE,
+    crypto_pwhash_argon2i_STRPREFIX,
+    crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE,
+    crypto_pwhash_argon2id_MEMLIMIT_MAX,
+    crypto_pwhash_argon2id_MEMLIMIT_MIN,
+    crypto_pwhash_argon2id_MEMLIMIT_MODERATE,
+    crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE,
+    crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE,
+    crypto_pwhash_argon2id_OPSLIMIT_MAX,
+    crypto_pwhash_argon2id_OPSLIMIT_MIN,
+    crypto_pwhash_argon2id_OPSLIMIT_MODERATE,
+    crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE,
+    crypto_pwhash_argon2id_STRPREFIX,
+    crypto_pwhash_scryptsalsa208sha256_BYTES_MAX,
+    crypto_pwhash_scryptsalsa208sha256_BYTES_MIN,
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE,
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX,
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN,
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE,
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE,
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX,
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN,
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE,
+    crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX,
+    crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN,
+    crypto_pwhash_scryptsalsa208sha256_SALTBYTES,
+    crypto_pwhash_scryptsalsa208sha256_STRBYTES,
+    crypto_pwhash_scryptsalsa208sha256_STRPREFIX,
+    crypto_pwhash_scryptsalsa208sha256_ll,
+    crypto_pwhash_scryptsalsa208sha256_str,
+    crypto_pwhash_scryptsalsa208sha256_str_verify,
+    crypto_pwhash_str_alg,
+    crypto_pwhash_str_verify,
+    has_crypto_pwhash_scryptsalsa208sha256,
+    nacl_bindings_pick_scrypt_params,
+)
+from nacl.bindings.crypto_scalarmult import (
+    crypto_scalarmult,
+    crypto_scalarmult_BYTES,
+    crypto_scalarmult_SCALARBYTES,
+    crypto_scalarmult_base,
+    crypto_scalarmult_ed25519,
+    crypto_scalarmult_ed25519_BYTES,
+    crypto_scalarmult_ed25519_SCALARBYTES,
+    crypto_scalarmult_ed25519_base,
+    crypto_scalarmult_ed25519_base_noclamp,
+    crypto_scalarmult_ed25519_noclamp,
+    has_crypto_scalarmult_ed25519,
+)
+from nacl.bindings.crypto_secretbox import (
+    crypto_secretbox,
+    crypto_secretbox_BOXZEROBYTES,
+    crypto_secretbox_KEYBYTES,
+    crypto_secretbox_MACBYTES,
+    crypto_secretbox_MESSAGEBYTES_MAX,
+    crypto_secretbox_NONCEBYTES,
+    crypto_secretbox_ZEROBYTES,
+    crypto_secretbox_open,
+)
+from nacl.bindings.crypto_secretstream import (
+    crypto_secretstream_xchacha20poly1305_ABYTES,
+    crypto_secretstream_xchacha20poly1305_HEADERBYTES,
+    crypto_secretstream_xchacha20poly1305_KEYBYTES,
+    crypto_secretstream_xchacha20poly1305_STATEBYTES,
+    crypto_secretstream_xchacha20poly1305_TAG_FINAL,
+    crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
+    crypto_secretstream_xchacha20poly1305_TAG_PUSH,
+    crypto_secretstream_xchacha20poly1305_TAG_REKEY,
+    crypto_secretstream_xchacha20poly1305_init_pull,
+    crypto_secretstream_xchacha20poly1305_init_push,
+    crypto_secretstream_xchacha20poly1305_keygen,
+    crypto_secretstream_xchacha20poly1305_pull,
+    crypto_secretstream_xchacha20poly1305_push,
+    crypto_secretstream_xchacha20poly1305_rekey,
+    crypto_secretstream_xchacha20poly1305_state,
+)
+from nacl.bindings.crypto_shorthash import (
+    BYTES as crypto_shorthash_siphash24_BYTES,
+    KEYBYTES as crypto_shorthash_siphash24_KEYBYTES,
+    XBYTES as crypto_shorthash_siphashx24_BYTES,
+    XKEYBYTES as crypto_shorthash_siphashx24_KEYBYTES,
+    crypto_shorthash_siphash24,
+    crypto_shorthash_siphashx24,
+    has_crypto_shorthash_siphashx24,
+)
+from nacl.bindings.crypto_sign import (
+    crypto_sign,
+    crypto_sign_BYTES,
+    crypto_sign_PUBLICKEYBYTES,
+    crypto_sign_SECRETKEYBYTES,
+    crypto_sign_SEEDBYTES,
+    crypto_sign_ed25519_pk_to_curve25519,
+    crypto_sign_ed25519_sk_to_curve25519,
+    crypto_sign_ed25519_sk_to_pk,
+    crypto_sign_ed25519_sk_to_seed,
+    crypto_sign_ed25519ph_STATEBYTES,
+    crypto_sign_ed25519ph_final_create,
+    crypto_sign_ed25519ph_final_verify,
+    crypto_sign_ed25519ph_state,
+    crypto_sign_ed25519ph_update,
+    crypto_sign_keypair,
+    crypto_sign_open,
+    crypto_sign_seed_keypair,
+)
+from nacl.bindings.randombytes import (
+    randombytes,
+    randombytes_buf_deterministic,
+)
+from nacl.bindings.sodium_core import sodium_init
+from nacl.bindings.utils import (
+    sodium_add,
+    sodium_increment,
+    sodium_memcmp,
+    sodium_pad,
+    sodium_unpad,
+)
+
+
+__all__ = [
+    "crypto_aead_chacha20poly1305_ABYTES",
+    "crypto_aead_chacha20poly1305_KEYBYTES",
+    "crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX",
+    "crypto_aead_chacha20poly1305_NPUBBYTES",
+    "crypto_aead_chacha20poly1305_NSECBYTES",
+    "crypto_aead_chacha20poly1305_decrypt",
+    "crypto_aead_chacha20poly1305_encrypt",
+    "crypto_aead_chacha20poly1305_ietf_ABYTES",
+    "crypto_aead_chacha20poly1305_ietf_KEYBYTES",
+    "crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX",
+    "crypto_aead_chacha20poly1305_ietf_NPUBBYTES",
+    "crypto_aead_chacha20poly1305_ietf_NSECBYTES",
+    "crypto_aead_chacha20poly1305_ietf_decrypt",
+    "crypto_aead_chacha20poly1305_ietf_encrypt",
+    "crypto_aead_xchacha20poly1305_ietf_ABYTES",
+    "crypto_aead_xchacha20poly1305_ietf_KEYBYTES",
+    "crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX",
+    "crypto_aead_xchacha20poly1305_ietf_NPUBBYTES",
+    "crypto_aead_xchacha20poly1305_ietf_NSECBYTES",
+    "crypto_aead_xchacha20poly1305_ietf_decrypt",
+    "crypto_aead_xchacha20poly1305_ietf_encrypt",
+    "crypto_box_SECRETKEYBYTES",
+    "crypto_box_PUBLICKEYBYTES",
+    "crypto_box_SEEDBYTES",
+    "crypto_box_NONCEBYTES",
+    "crypto_box_ZEROBYTES",
+    "crypto_box_BOXZEROBYTES",
+    "crypto_box_BEFORENMBYTES",
+    "crypto_box_SEALBYTES",
+    "crypto_box_keypair",
+    "crypto_box",
+    "crypto_box_open",
+    "crypto_box_beforenm",
+    "crypto_box_afternm",
+    "crypto_box_open_afternm",
+    "crypto_box_seal",
+    "crypto_box_seal_open",
+    "crypto_box_seed_keypair",
+    "has_crypto_core_ed25519",
+    "crypto_core_ed25519_BYTES",
+    "crypto_core_ed25519_UNIFORMBYTES",
+    "crypto_core_ed25519_SCALARBYTES",
+    "crypto_core_ed25519_NONREDUCEDSCALARBYTES",
+    "crypto_core_ed25519_add",
+    "crypto_core_ed25519_from_uniform",
+    "crypto_core_ed25519_is_valid_point",
+    "crypto_core_ed25519_sub",
+    "crypto_core_ed25519_scalar_invert",
+    "crypto_core_ed25519_scalar_negate",
+    "crypto_core_ed25519_scalar_complement",
+    "crypto_core_ed25519_scalar_add",
+    "crypto_core_ed25519_scalar_sub",
+    "crypto_core_ed25519_scalar_mul",
+    "crypto_core_ed25519_scalar_reduce",
+    "crypto_hash_BYTES",
+    "crypto_hash_sha256_BYTES",
+    "crypto_hash_sha512_BYTES",
+    "crypto_hash",
+    "crypto_hash_sha256",
+    "crypto_hash_sha512",
+    "crypto_generichash_BYTES",
+    "crypto_generichash_BYTES_MIN",
+    "crypto_generichash_BYTES_MAX",
+    "crypto_generichash_KEYBYTES",
+    "crypto_generichash_KEYBYTES_MIN",
+    "crypto_generichash_KEYBYTES_MAX",
+    "crypto_generichash_SALTBYTES",
+    "crypto_generichash_PERSONALBYTES",
+    "crypto_generichash_STATEBYTES",
+    "crypto_generichash_blake2b_salt_personal",
+    "crypto_generichash_blake2b_init",
+    "crypto_generichash_blake2b_update",
+    "crypto_generichash_blake2b_final",
+    "crypto_kx_keypair",
+    "crypto_kx_seed_keypair",
+    "crypto_kx_client_session_keys",
+    "crypto_kx_server_session_keys",
+    "crypto_kx_PUBLIC_KEY_BYTES",
+    "crypto_kx_SECRET_KEY_BYTES",
+    "crypto_kx_SEED_BYTES",
+    "crypto_kx_SESSION_KEY_BYTES",
+    "has_crypto_scalarmult_ed25519",
+    "crypto_scalarmult_BYTES",
+    "crypto_scalarmult_SCALARBYTES",
+    "crypto_scalarmult",
+    "crypto_scalarmult_base",
+    "crypto_scalarmult_ed25519_BYTES",
+    "crypto_scalarmult_ed25519_SCALARBYTES",
+    "crypto_scalarmult_ed25519",
+    "crypto_scalarmult_ed25519_base",
+    "crypto_scalarmult_ed25519_noclamp",
+    "crypto_scalarmult_ed25519_base_noclamp",
+    "crypto_secretbox_KEYBYTES",
+    "crypto_secretbox_NONCEBYTES",
+    "crypto_secretbox_ZEROBYTES",
+    "crypto_secretbox_BOXZEROBYTES",
+    "crypto_secretbox_MACBYTES",
+    "crypto_secretbox_MESSAGEBYTES_MAX",
+    "crypto_secretbox",
+    "crypto_secretbox_open",
+    "crypto_secretstream_xchacha20poly1305_ABYTES",
+    "crypto_secretstream_xchacha20poly1305_HEADERBYTES",
+    "crypto_secretstream_xchacha20poly1305_KEYBYTES",
+    "crypto_secretstream_xchacha20poly1305_STATEBYTES",
+    "crypto_secretstream_xchacha20poly1305_TAG_FINAL",
+    "crypto_secretstream_xchacha20poly1305_TAG_MESSAGE",
+    "crypto_secretstream_xchacha20poly1305_TAG_PUSH",
+    "crypto_secretstream_xchacha20poly1305_TAG_REKEY",
+    "crypto_secretstream_xchacha20poly1305_init_pull",
+    "crypto_secretstream_xchacha20poly1305_init_push",
+    "crypto_secretstream_xchacha20poly1305_keygen",
+    "crypto_secretstream_xchacha20poly1305_pull",
+    "crypto_secretstream_xchacha20poly1305_push",
+    "crypto_secretstream_xchacha20poly1305_rekey",
+    "crypto_secretstream_xchacha20poly1305_state",
+    "has_crypto_shorthash_siphashx24",
+    "crypto_shorthash_siphash24_BYTES",
+    "crypto_shorthash_siphash24_KEYBYTES",
+    "crypto_shorthash_siphash24",
+    "crypto_shorthash_siphashx24_BYTES",
+    "crypto_shorthash_siphashx24_KEYBYTES",
+    "crypto_shorthash_siphashx24",
+    "crypto_sign_BYTES",
+    "crypto_sign_SEEDBYTES",
+    "crypto_sign_PUBLICKEYBYTES",
+    "crypto_sign_SECRETKEYBYTES",
+    "crypto_sign_keypair",
+    "crypto_sign_seed_keypair",
+    "crypto_sign",
+    "crypto_sign_open",
+    "crypto_sign_ed25519_pk_to_curve25519",
+    "crypto_sign_ed25519_sk_to_curve25519",
+    "crypto_sign_ed25519_sk_to_pk",
+    "crypto_sign_ed25519_sk_to_seed",
+    "crypto_sign_ed25519ph_STATEBYTES",
+    "crypto_sign_ed25519ph_final_create",
+    "crypto_sign_ed25519ph_final_verify",
+    "crypto_sign_ed25519ph_state",
+    "crypto_sign_ed25519ph_update",
+    "crypto_pwhash_ALG_ARGON2I13",
+    "crypto_pwhash_ALG_ARGON2ID13",
+    "crypto_pwhash_ALG_DEFAULT",
+    "crypto_pwhash_BYTES_MAX",
+    "crypto_pwhash_BYTES_MIN",
+    "crypto_pwhash_PASSWD_MAX",
+    "crypto_pwhash_PASSWD_MIN",
+    "crypto_pwhash_SALTBYTES",
+    "crypto_pwhash_STRBYTES",
+    "crypto_pwhash_alg",
+    "crypto_pwhash_argon2i_MEMLIMIT_MIN",
+    "crypto_pwhash_argon2i_MEMLIMIT_MAX",
+    "crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE",
+    "crypto_pwhash_argon2i_MEMLIMIT_MODERATE",
+    "crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE",
+    "crypto_pwhash_argon2i_OPSLIMIT_MIN",
+    "crypto_pwhash_argon2i_OPSLIMIT_MAX",
+    "crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE",
+    "crypto_pwhash_argon2i_OPSLIMIT_MODERATE",
+    "crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE",
+    "crypto_pwhash_argon2i_STRPREFIX",
+    "crypto_pwhash_argon2id_MEMLIMIT_MIN",
+    "crypto_pwhash_argon2id_MEMLIMIT_MAX",
+    "crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE",
+    "crypto_pwhash_argon2id_MEMLIMIT_MODERATE",
+    "crypto_pwhash_argon2id_OPSLIMIT_MIN",
+    "crypto_pwhash_argon2id_OPSLIMIT_MAX",
+    "crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE",
+    "crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE",
+    "crypto_pwhash_argon2id_OPSLIMIT_MODERATE",
+    "crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE",
+    "crypto_pwhash_argon2id_STRPREFIX",
+    "crypto_pwhash_str_alg",
+    "crypto_pwhash_str_verify",
+    "has_crypto_pwhash_scryptsalsa208sha256",
+    "crypto_pwhash_scryptsalsa208sha256_BYTES_MAX",
+    "crypto_pwhash_scryptsalsa208sha256_BYTES_MIN",
+    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE",
+    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX",
+    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN",
+    "crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE",
+    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE",
+    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX",
+    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN",
+    "crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE",
+    "crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX",
+    "crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN",
+    "crypto_pwhash_scryptsalsa208sha256_SALTBYTES",
+    "crypto_pwhash_scryptsalsa208sha256_STRBYTES",
+    "crypto_pwhash_scryptsalsa208sha256_STRPREFIX",
+    "crypto_pwhash_scryptsalsa208sha256_ll",
+    "crypto_pwhash_scryptsalsa208sha256_str",
+    "crypto_pwhash_scryptsalsa208sha256_str_verify",
+    "nacl_bindings_pick_scrypt_params",
+    "randombytes",
+    "randombytes_buf_deterministic",
+    "sodium_init",
+    "sodium_add",
+    "sodium_increment",
+    "sodium_memcmp",
+    "sodium_pad",
+    "sodium_unpad",
+]
+
+
+# Initialize Sodium
+sodium_init()
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_aead.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_aead.py
new file mode 100644
index 00000000..2e7168db
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_aead.py
@@ -0,0 +1,559 @@
+# Copyright 2017 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import Optional
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+"""
+Implementations of authenticated encription with associated data (*AEAD*)
+constructions building on the chacha20 stream cipher and the poly1305
+authenticator
+"""
+
+crypto_aead_chacha20poly1305_ietf_KEYBYTES: int = (
+    lib.crypto_aead_chacha20poly1305_ietf_keybytes()
+)
+crypto_aead_chacha20poly1305_ietf_NSECBYTES: int = (
+    lib.crypto_aead_chacha20poly1305_ietf_nsecbytes()
+)
+crypto_aead_chacha20poly1305_ietf_NPUBBYTES: int = (
+    lib.crypto_aead_chacha20poly1305_ietf_npubbytes()
+)
+crypto_aead_chacha20poly1305_ietf_ABYTES: int = (
+    lib.crypto_aead_chacha20poly1305_ietf_abytes()
+)
+crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX: int = (
+    lib.crypto_aead_chacha20poly1305_ietf_messagebytes_max()
+)
+_aead_chacha20poly1305_ietf_CRYPTBYTES_MAX = (
+    crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX
+    + crypto_aead_chacha20poly1305_ietf_ABYTES
+)
+
+crypto_aead_chacha20poly1305_KEYBYTES: int = (
+    lib.crypto_aead_chacha20poly1305_keybytes()
+)
+crypto_aead_chacha20poly1305_NSECBYTES: int = (
+    lib.crypto_aead_chacha20poly1305_nsecbytes()
+)
+crypto_aead_chacha20poly1305_NPUBBYTES: int = (
+    lib.crypto_aead_chacha20poly1305_npubbytes()
+)
+crypto_aead_chacha20poly1305_ABYTES: int = (
+    lib.crypto_aead_chacha20poly1305_abytes()
+)
+crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX: int = (
+    lib.crypto_aead_chacha20poly1305_messagebytes_max()
+)
+_aead_chacha20poly1305_CRYPTBYTES_MAX = (
+    crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX
+    + crypto_aead_chacha20poly1305_ABYTES
+)
+
+crypto_aead_xchacha20poly1305_ietf_KEYBYTES: int = (
+    lib.crypto_aead_xchacha20poly1305_ietf_keybytes()
+)
+crypto_aead_xchacha20poly1305_ietf_NSECBYTES: int = (
+    lib.crypto_aead_xchacha20poly1305_ietf_nsecbytes()
+)
+crypto_aead_xchacha20poly1305_ietf_NPUBBYTES: int = (
+    lib.crypto_aead_xchacha20poly1305_ietf_npubbytes()
+)
+crypto_aead_xchacha20poly1305_ietf_ABYTES: int = (
+    lib.crypto_aead_xchacha20poly1305_ietf_abytes()
+)
+crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX: int = (
+    lib.crypto_aead_xchacha20poly1305_ietf_messagebytes_max()
+)
+_aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX = (
+    crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX
+    + crypto_aead_xchacha20poly1305_ietf_ABYTES
+)
+
+
+def crypto_aead_chacha20poly1305_ietf_encrypt(
+    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
+) -> bytes:
+    """
+    Encrypt the given ``message`` using the IETF ratified chacha20poly1305
+    construction described in RFC7539.
+
+    :param message:
+    :type message: bytes
+    :param aad:
+    :type aad: Optional[bytes]
+    :param nonce:
+    :type nonce: bytes
+    :param key:
+    :type key: bytes
+    :return: authenticated ciphertext
+    :rtype: bytes
+    """
+    ensure(
+        isinstance(message, bytes),
+        "Input message type must be bytes",
+        raising=exc.TypeError,
+    )
+
+    mlen = len(message)
+
+    ensure(
+        mlen <= crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX,
+        "Message must be at most {} bytes long".format(
+            crypto_aead_chacha20poly1305_ietf_MESSAGEBYTES_MAX
+        ),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        isinstance(aad, bytes) or (aad is None),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(nonce, bytes)
+        and len(nonce) == crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
+        "Nonce must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_ietf_NPUBBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(key, bytes)
+        and len(key) == crypto_aead_chacha20poly1305_ietf_KEYBYTES,
+        "Key must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_ietf_KEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    if aad:
+        _aad = aad
+        aalen = len(aad)
+    else:
+        _aad = ffi.NULL
+        aalen = 0
+
+    mxout = mlen + crypto_aead_chacha20poly1305_ietf_ABYTES
+
+    clen = ffi.new("unsigned long long *")
+
+    ciphertext = ffi.new("unsigned char[]", mxout)
+
+    res = lib.crypto_aead_chacha20poly1305_ietf_encrypt(
+        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
+    )
+
+    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
+    return ffi.buffer(ciphertext, clen[0])[:]
+
+
+def crypto_aead_chacha20poly1305_ietf_decrypt(
+    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
+) -> bytes:
+    """
+    Decrypt the given ``ciphertext`` using the IETF ratified chacha20poly1305
+    construction described in RFC7539.
+
+    :param ciphertext:
+    :type ciphertext: bytes
+    :param aad:
+    :type aad: Optional[bytes]
+    :param nonce:
+    :type nonce: bytes
+    :param key:
+    :type key: bytes
+    :return: message
+    :rtype: bytes
+    """
+    ensure(
+        isinstance(ciphertext, bytes),
+        "Input ciphertext type must be bytes",
+        raising=exc.TypeError,
+    )
+
+    clen = len(ciphertext)
+
+    ensure(
+        clen <= _aead_chacha20poly1305_ietf_CRYPTBYTES_MAX,
+        "Ciphertext must be at most {} bytes long".format(
+            _aead_chacha20poly1305_ietf_CRYPTBYTES_MAX
+        ),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        isinstance(aad, bytes) or (aad is None),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(nonce, bytes)
+        and len(nonce) == crypto_aead_chacha20poly1305_ietf_NPUBBYTES,
+        "Nonce must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_ietf_NPUBBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(key, bytes)
+        and len(key) == crypto_aead_chacha20poly1305_ietf_KEYBYTES,
+        "Key must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_ietf_KEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    mxout = clen - crypto_aead_chacha20poly1305_ietf_ABYTES
+
+    mlen = ffi.new("unsigned long long *")
+    message = ffi.new("unsigned char[]", mxout)
+
+    if aad:
+        _aad = aad
+        aalen = len(aad)
+    else:
+        _aad = ffi.NULL
+        aalen = 0
+
+    res = lib.crypto_aead_chacha20poly1305_ietf_decrypt(
+        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
+    )
+
+    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)
+
+    return ffi.buffer(message, mlen[0])[:]
+
+
+def crypto_aead_chacha20poly1305_encrypt(
+    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
+) -> bytes:
+    """
+    Encrypt the given ``message`` using the "legacy" construction
+    described in draft-agl-tls-chacha20poly1305.
+
+    :param message:
+    :type message: bytes
+    :param aad:
+    :type aad: Optional[bytes]
+    :param nonce:
+    :type nonce: bytes
+    :param key:
+    :type key: bytes
+    :return: authenticated ciphertext
+    :rtype: bytes
+    """
+    ensure(
+        isinstance(message, bytes),
+        "Input message type must be bytes",
+        raising=exc.TypeError,
+    )
+
+    mlen = len(message)
+
+    ensure(
+        mlen <= crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX,
+        "Message must be at most {} bytes long".format(
+            crypto_aead_chacha20poly1305_MESSAGEBYTES_MAX
+        ),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        isinstance(aad, bytes) or (aad is None),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(nonce, bytes)
+        and len(nonce) == crypto_aead_chacha20poly1305_NPUBBYTES,
+        "Nonce must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_NPUBBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(key, bytes)
+        and len(key) == crypto_aead_chacha20poly1305_KEYBYTES,
+        "Key must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_KEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    if aad:
+        _aad = aad
+        aalen = len(aad)
+    else:
+        _aad = ffi.NULL
+        aalen = 0
+
+    mlen = len(message)
+    mxout = mlen + crypto_aead_chacha20poly1305_ietf_ABYTES
+
+    clen = ffi.new("unsigned long long *")
+
+    ciphertext = ffi.new("unsigned char[]", mxout)
+
+    res = lib.crypto_aead_chacha20poly1305_encrypt(
+        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
+    )
+
+    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
+    return ffi.buffer(ciphertext, clen[0])[:]
+
+
+def crypto_aead_chacha20poly1305_decrypt(
+    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
+) -> bytes:
+    """
+    Decrypt the given ``ciphertext`` using the "legacy" construction
+    described in draft-agl-tls-chacha20poly1305.
+
+    :param ciphertext: authenticated ciphertext
+    :type ciphertext: bytes
+    :param aad:
+    :type aad: Optional[bytes]
+    :param nonce:
+    :type nonce: bytes
+    :param key:
+    :type key: bytes
+    :return: message
+    :rtype: bytes
+    """
+    ensure(
+        isinstance(ciphertext, bytes),
+        "Input ciphertext type must be bytes",
+        raising=exc.TypeError,
+    )
+
+    clen = len(ciphertext)
+
+    ensure(
+        clen <= _aead_chacha20poly1305_CRYPTBYTES_MAX,
+        "Ciphertext must be at most {} bytes long".format(
+            _aead_chacha20poly1305_CRYPTBYTES_MAX
+        ),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        isinstance(aad, bytes) or (aad is None),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(nonce, bytes)
+        and len(nonce) == crypto_aead_chacha20poly1305_NPUBBYTES,
+        "Nonce must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_NPUBBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(key, bytes)
+        and len(key) == crypto_aead_chacha20poly1305_KEYBYTES,
+        "Key must be a {} bytes long bytes sequence".format(
+            crypto_aead_chacha20poly1305_KEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    mxout = clen - crypto_aead_chacha20poly1305_ABYTES
+
+    mlen = ffi.new("unsigned long long *")
+    message = ffi.new("unsigned char[]", mxout)
+
+    if aad:
+        _aad = aad
+        aalen = len(aad)
+    else:
+        _aad = ffi.NULL
+        aalen = 0
+
+    res = lib.crypto_aead_chacha20poly1305_decrypt(
+        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
+    )
+
+    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)
+
+    return ffi.buffer(message, mlen[0])[:]
+
+
+def crypto_aead_xchacha20poly1305_ietf_encrypt(
+    message: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
+) -> bytes:
+    """
+    Encrypt the given ``message`` using the long-nonces xchacha20poly1305
+    construction.
+
+    :param message:
+    :type message: bytes
+    :param aad:
+    :type aad: Optional[bytes]
+    :param nonce:
+    :type nonce: bytes
+    :param key:
+    :type key: bytes
+    :return: authenticated ciphertext
+    :rtype: bytes
+    """
+    ensure(
+        isinstance(message, bytes),
+        "Input message type must be bytes",
+        raising=exc.TypeError,
+    )
+
+    mlen = len(message)
+
+    ensure(
+        mlen <= crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX,
+        "Message must be at most {} bytes long".format(
+            crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX
+        ),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        isinstance(aad, bytes) or (aad is None),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(nonce, bytes)
+        and len(nonce) == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
+        "Nonce must be a {} bytes long bytes sequence".format(
+            crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(key, bytes)
+        and len(key) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
+        "Key must be a {} bytes long bytes sequence".format(
+            crypto_aead_xchacha20poly1305_ietf_KEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    if aad:
+        _aad = aad
+        aalen = len(aad)
+    else:
+        _aad = ffi.NULL
+        aalen = 0
+
+    mlen = len(message)
+    mxout = mlen + crypto_aead_xchacha20poly1305_ietf_ABYTES
+
+    clen = ffi.new("unsigned long long *")
+
+    ciphertext = ffi.new("unsigned char[]", mxout)
+
+    res = lib.crypto_aead_xchacha20poly1305_ietf_encrypt(
+        ciphertext, clen, message, mlen, _aad, aalen, ffi.NULL, nonce, key
+    )
+
+    ensure(res == 0, "Encryption failed.", raising=exc.CryptoError)
+    return ffi.buffer(ciphertext, clen[0])[:]
+
+
+def crypto_aead_xchacha20poly1305_ietf_decrypt(
+    ciphertext: bytes, aad: Optional[bytes], nonce: bytes, key: bytes
+) -> bytes:
+    """
+    Decrypt the given ``ciphertext`` using the long-nonces xchacha20poly1305
+    construction.
+
+    :param ciphertext: authenticated ciphertext
+    :type ciphertext: bytes
+    :param aad:
+    :type aad: Optional[bytes]
+    :param nonce:
+    :type nonce: bytes
+    :param key:
+    :type key: bytes
+    :return: message
+    :rtype: bytes
+    """
+    ensure(
+        isinstance(ciphertext, bytes),
+        "Input ciphertext type must be bytes",
+        raising=exc.TypeError,
+    )
+
+    clen = len(ciphertext)
+
+    ensure(
+        clen <= _aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX,
+        "Ciphertext must be at most {} bytes long".format(
+            _aead_xchacha20poly1305_ietf_CRYPTBYTES_MAX
+        ),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        isinstance(aad, bytes) or (aad is None),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(nonce, bytes)
+        and len(nonce) == crypto_aead_xchacha20poly1305_ietf_NPUBBYTES,
+        "Nonce must be a {} bytes long bytes sequence".format(
+            crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(key, bytes)
+        and len(key) == crypto_aead_xchacha20poly1305_ietf_KEYBYTES,
+        "Key must be a {} bytes long bytes sequence".format(
+            crypto_aead_xchacha20poly1305_ietf_KEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    mxout = clen - crypto_aead_xchacha20poly1305_ietf_ABYTES
+    mlen = ffi.new("unsigned long long *")
+    message = ffi.new("unsigned char[]", mxout)
+
+    if aad:
+        _aad = aad
+        aalen = len(aad)
+    else:
+        _aad = ffi.NULL
+        aalen = 0
+
+    res = lib.crypto_aead_xchacha20poly1305_ietf_decrypt(
+        message, mlen, ffi.NULL, ciphertext, clen, _aad, aalen, nonce, key
+    )
+
+    ensure(res == 0, "Decryption failed.", raising=exc.CryptoError)
+
+    return ffi.buffer(message, mlen[0])[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_box.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_box.py
new file mode 100644
index 00000000..74f7f0a6
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_box.py
@@ -0,0 +1,324 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import Tuple
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+__all__ = ["crypto_box_keypair", "crypto_box"]
+
+
+crypto_box_SECRETKEYBYTES: int = lib.crypto_box_secretkeybytes()
+crypto_box_PUBLICKEYBYTES: int = lib.crypto_box_publickeybytes()
+crypto_box_SEEDBYTES: int = lib.crypto_box_seedbytes()
+crypto_box_NONCEBYTES: int = lib.crypto_box_noncebytes()
+crypto_box_ZEROBYTES: int = lib.crypto_box_zerobytes()
+crypto_box_BOXZEROBYTES: int = lib.crypto_box_boxzerobytes()
+crypto_box_BEFORENMBYTES: int = lib.crypto_box_beforenmbytes()
+crypto_box_SEALBYTES: int = lib.crypto_box_sealbytes()
+
+
+def crypto_box_keypair() -> Tuple[bytes, bytes]:
+    """
+    Returns a randomly generated public and secret key.
+
+    :rtype: (bytes(public_key), bytes(secret_key))
+    """
+    pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
+    sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)
+
+    rc = lib.crypto_box_keypair(pk, sk)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return (
+        ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
+        ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
+    )
+
+
+def crypto_box_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
+    """
+    Returns a (public, secret) keypair deterministically generated
+    from an input ``seed``.
+
+    .. warning:: The seed **must** be high-entropy; therefore,
+        its generator **must** be a cryptographic quality
+        random function like, for example, :func:`~nacl.utils.random`.
+
+    .. warning:: The seed **must** be protected and remain secret.
+        Anyone who knows the seed is really in possession of
+        the corresponding PrivateKey.
+
+
+    :param seed: bytes
+    :rtype: (bytes(public_key), bytes(secret_key))
+    """
+    ensure(isinstance(seed, bytes), "seed must be bytes", raising=TypeError)
+
+    if len(seed) != crypto_box_SEEDBYTES:
+        raise exc.ValueError("Invalid seed")
+
+    pk = ffi.new("unsigned char[]", crypto_box_PUBLICKEYBYTES)
+    sk = ffi.new("unsigned char[]", crypto_box_SECRETKEYBYTES)
+
+    rc = lib.crypto_box_seed_keypair(pk, sk, seed)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return (
+        ffi.buffer(pk, crypto_box_PUBLICKEYBYTES)[:],
+        ffi.buffer(sk, crypto_box_SECRETKEYBYTES)[:],
+    )
+
+
+def crypto_box(message: bytes, nonce: bytes, pk: bytes, sk: bytes) -> bytes:
+    """
+    Encrypts and returns a message ``message`` using the secret key ``sk``,
+    public key ``pk``, and the nonce ``nonce``.
+
+    :param message: bytes
+    :param nonce: bytes
+    :param pk: bytes
+    :param sk: bytes
+    :rtype: bytes
+    """
+    if len(nonce) != crypto_box_NONCEBYTES:
+        raise exc.ValueError("Invalid nonce size")
+
+    if len(pk) != crypto_box_PUBLICKEYBYTES:
+        raise exc.ValueError("Invalid public key")
+
+    if len(sk) != crypto_box_SECRETKEYBYTES:
+        raise exc.ValueError("Invalid secret key")
+
+    padded = (b"\x00" * crypto_box_ZEROBYTES) + message
+    ciphertext = ffi.new("unsigned char[]", len(padded))
+
+    rc = lib.crypto_box(ciphertext, padded, len(padded), nonce, pk, sk)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
+
+
+def crypto_box_open(
+    ciphertext: bytes, nonce: bytes, pk: bytes, sk: bytes
+) -> bytes:
+    """
+    Decrypts and returns an encrypted message ``ciphertext``, using the secret
+    key ``sk``, public key ``pk``, and the nonce ``nonce``.
+
+    :param ciphertext: bytes
+    :param nonce: bytes
+    :param pk: bytes
+    :param sk: bytes
+    :rtype: bytes
+    """
+    if len(nonce) != crypto_box_NONCEBYTES:
+        raise exc.ValueError("Invalid nonce size")
+
+    if len(pk) != crypto_box_PUBLICKEYBYTES:
+        raise exc.ValueError("Invalid public key")
+
+    if len(sk) != crypto_box_SECRETKEYBYTES:
+        raise exc.ValueError("Invalid secret key")
+
+    padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
+    plaintext = ffi.new("unsigned char[]", len(padded))
+
+    res = lib.crypto_box_open(plaintext, padded, len(padded), nonce, pk, sk)
+    ensure(
+        res == 0,
+        "An error occurred trying to decrypt the message",
+        raising=exc.CryptoError,
+    )
+
+    return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
+
+
+def crypto_box_beforenm(pk: bytes, sk: bytes) -> bytes:
+    """
+    Computes and returns the shared key for the public key ``pk`` and the
+    secret key ``sk``. This can be used to speed up operations where the same
+    set of keys is going to be used multiple times.
+
+    :param pk: bytes
+    :param sk: bytes
+    :rtype: bytes
+    """
+    if len(pk) != crypto_box_PUBLICKEYBYTES:
+        raise exc.ValueError("Invalid public key")
+
+    if len(sk) != crypto_box_SECRETKEYBYTES:
+        raise exc.ValueError("Invalid secret key")
+
+    k = ffi.new("unsigned char[]", crypto_box_BEFORENMBYTES)
+
+    rc = lib.crypto_box_beforenm(k, pk, sk)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(k, crypto_box_BEFORENMBYTES)[:]
+
+
+def crypto_box_afternm(message: bytes, nonce: bytes, k: bytes) -> bytes:
+    """
+    Encrypts and returns the message ``message`` using the shared key ``k`` and
+    the nonce ``nonce``.
+
+    :param message: bytes
+    :param nonce: bytes
+    :param k: bytes
+    :rtype: bytes
+    """
+    if len(nonce) != crypto_box_NONCEBYTES:
+        raise exc.ValueError("Invalid nonce")
+
+    if len(k) != crypto_box_BEFORENMBYTES:
+        raise exc.ValueError("Invalid shared key")
+
+    padded = b"\x00" * crypto_box_ZEROBYTES + message
+    ciphertext = ffi.new("unsigned char[]", len(padded))
+
+    rc = lib.crypto_box_afternm(ciphertext, padded, len(padded), nonce, k)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(ciphertext, len(padded))[crypto_box_BOXZEROBYTES:]
+
+
+def crypto_box_open_afternm(
+    ciphertext: bytes, nonce: bytes, k: bytes
+) -> bytes:
+    """
+    Decrypts and returns the encrypted message ``ciphertext``, using the shared
+    key ``k`` and the nonce ``nonce``.
+
+    :param ciphertext: bytes
+    :param nonce: bytes
+    :param k: bytes
+    :rtype: bytes
+    """
+    if len(nonce) != crypto_box_NONCEBYTES:
+        raise exc.ValueError("Invalid nonce")
+
+    if len(k) != crypto_box_BEFORENMBYTES:
+        raise exc.ValueError("Invalid shared key")
+
+    padded = (b"\x00" * crypto_box_BOXZEROBYTES) + ciphertext
+    plaintext = ffi.new("unsigned char[]", len(padded))
+
+    res = lib.crypto_box_open_afternm(plaintext, padded, len(padded), nonce, k)
+    ensure(
+        res == 0,
+        "An error occurred trying to decrypt the message",
+        raising=exc.CryptoError,
+    )
+
+    return ffi.buffer(plaintext, len(padded))[crypto_box_ZEROBYTES:]
+
+
+def crypto_box_seal(message: bytes, pk: bytes) -> bytes:
+    """
+    Encrypts and returns a message ``message`` using an ephemeral secret key
+    and the public key ``pk``.
+    The ephemeral public key, which is embedded in the sealed box, is also
+    used, in combination with ``pk``, to derive the nonce needed for the
+    underlying box construct.
+
+    :param message: bytes
+    :param pk: bytes
+    :rtype: bytes
+
+    .. versionadded:: 1.2
+    """
+    ensure(
+        isinstance(message, bytes),
+        "input message must be bytes",
+        raising=TypeError,
+    )
+
+    ensure(
+        isinstance(pk, bytes), "public key must be bytes", raising=TypeError
+    )
+
+    if len(pk) != crypto_box_PUBLICKEYBYTES:
+        raise exc.ValueError("Invalid public key")
+
+    _mlen = len(message)
+    _clen = crypto_box_SEALBYTES + _mlen
+
+    ciphertext = ffi.new("unsigned char[]", _clen)
+
+    rc = lib.crypto_box_seal(ciphertext, message, _mlen, pk)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(ciphertext, _clen)[:]
+
+
+def crypto_box_seal_open(ciphertext: bytes, pk: bytes, sk: bytes) -> bytes:
+    """
+    Decrypts and returns an encrypted message ``ciphertext``, using the
+    recipent's secret key ``sk`` and the sender's ephemeral public key
+    embedded in the sealed box. The box contruct nonce is derived from
+    the recipient's public key ``pk`` and the sender's public key.
+
+    :param ciphertext: bytes
+    :param pk: bytes
+    :param sk: bytes
+    :rtype: bytes
+
+    .. versionadded:: 1.2
+    """
+    ensure(
+        isinstance(ciphertext, bytes),
+        "input ciphertext must be bytes",
+        raising=TypeError,
+    )
+
+    ensure(
+        isinstance(pk, bytes), "public key must be bytes", raising=TypeError
+    )
+
+    ensure(
+        isinstance(sk, bytes), "secret key must be bytes", raising=TypeError
+    )
+
+    if len(pk) != crypto_box_PUBLICKEYBYTES:
+        raise exc.ValueError("Invalid public key")
+
+    if len(sk) != crypto_box_SECRETKEYBYTES:
+        raise exc.ValueError("Invalid secret key")
+
+    _clen = len(ciphertext)
+
+    ensure(
+        _clen >= crypto_box_SEALBYTES,
+        ("Input cyphertext must be at least {} long").format(
+            crypto_box_SEALBYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    _mlen = _clen - crypto_box_SEALBYTES
+
+    # zero-length malloc results are implementation.dependent
+    plaintext = ffi.new("unsigned char[]", max(1, _mlen))
+
+    res = lib.crypto_box_seal_open(plaintext, ciphertext, _clen, pk, sk)
+    ensure(
+        res == 0,
+        "An error occurred trying to decrypt the message",
+        raising=exc.CryptoError,
+    )
+
+    return ffi.buffer(plaintext, _mlen)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_core.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_core.py
new file mode 100644
index 00000000..d29da9b3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_core.py
@@ -0,0 +1,412 @@
+# Copyright 2018 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+has_crypto_core_ed25519 = bool(lib.PYNACL_HAS_CRYPTO_CORE_ED25519)
+
+crypto_core_ed25519_BYTES = 0
+crypto_core_ed25519_SCALARBYTES = 0
+crypto_core_ed25519_NONREDUCEDSCALARBYTES = 0
+
+if has_crypto_core_ed25519:
+    crypto_core_ed25519_BYTES = lib.crypto_core_ed25519_bytes()
+    crypto_core_ed25519_SCALARBYTES = lib.crypto_core_ed25519_scalarbytes()
+    crypto_core_ed25519_NONREDUCEDSCALARBYTES = (
+        lib.crypto_core_ed25519_nonreducedscalarbytes()
+    )
+
+
+def crypto_core_ed25519_is_valid_point(p: bytes) -> bool:
+    """
+    Check if ``p`` represents a point on the edwards25519 curve, in canonical
+    form, on the main subgroup, and that the point doesn't have a small order.
+
+    :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
+              representing a point on the edwards25519 curve
+    :type p: bytes
+    :return: point validity
+    :rtype: bool
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(p, bytes) and len(p) == crypto_core_ed25519_BYTES,
+        "Point must be a crypto_core_ed25519_BYTES long bytes sequence",
+        raising=exc.TypeError,
+    )
+
+    rc = lib.crypto_core_ed25519_is_valid_point(p)
+    return rc == 1
+
+
+def crypto_core_ed25519_add(p: bytes, q: bytes) -> bytes:
+    """
+    Add two points on the edwards25519 curve.
+
+    :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
+              representing a point on the edwards25519 curve
+    :type p: bytes
+    :param q: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
+              representing a point on the edwards25519 curve
+    :type q: bytes
+    :return: a point on the edwards25519 curve represented as
+             a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(p, bytes)
+        and isinstance(q, bytes)
+        and len(p) == crypto_core_ed25519_BYTES
+        and len(q) == crypto_core_ed25519_BYTES,
+        "Each point must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_BYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)
+
+    rc = lib.crypto_core_ed25519_add(r, p, q)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(r, crypto_core_ed25519_BYTES)[:]
+
+
+def crypto_core_ed25519_sub(p: bytes, q: bytes) -> bytes:
+    """
+    Subtract a point from another on the edwards25519 curve.
+
+    :param p: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
+              representing a point on the edwards25519 curve
+    :type p: bytes
+    :param q: a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
+              representing a point on the edwards25519 curve
+    :type q: bytes
+    :return: a point on the edwards25519 curve represented as
+             a :py:data:`.crypto_core_ed25519_BYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(p, bytes)
+        and isinstance(q, bytes)
+        and len(p) == crypto_core_ed25519_BYTES
+        and len(q) == crypto_core_ed25519_BYTES,
+        "Each point must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_BYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_BYTES)
+
+    rc = lib.crypto_core_ed25519_sub(r, p, q)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(r, crypto_core_ed25519_BYTES)[:]
+
+
+def crypto_core_ed25519_scalar_invert(s: bytes) -> bytes:
+    """
+    Return the multiplicative inverse of integer ``s`` modulo ``L``,
+    i.e an integer ``i`` such that ``s * i = 1 (mod L)``, where ``L``
+    is the order of the main subgroup.
+
+    Raises a ``exc.RuntimeError`` if ``s`` is the integer zero.
+
+    :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type s: bytes
+    :return: an integer represented as a
+              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
+        "Integer s must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
+
+    rc = lib.crypto_core_ed25519_scalar_invert(r, s)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
+
+
+def crypto_core_ed25519_scalar_negate(s: bytes) -> bytes:
+    """
+    Return the integer ``n`` such that ``s + n = 0 (mod L)``, where ``L``
+    is the order of the main subgroup.
+
+    :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type s: bytes
+    :return: an integer represented as a
+              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
+        "Integer s must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
+
+    lib.crypto_core_ed25519_scalar_negate(r, s)
+
+    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
+
+
+def crypto_core_ed25519_scalar_complement(s: bytes) -> bytes:
+    """
+    Return the complement of integer ``s`` modulo ``L``, i.e. an integer
+    ``c`` such that ``s + c = 1 (mod L)``, where ``L`` is the order of
+    the main subgroup.
+
+    :param s: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type s: bytes
+    :return: an integer represented as a
+              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(s, bytes) and len(s) == crypto_core_ed25519_SCALARBYTES,
+        "Integer s must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
+
+    lib.crypto_core_ed25519_scalar_complement(r, s)
+
+    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
+
+
+def crypto_core_ed25519_scalar_add(p: bytes, q: bytes) -> bytes:
+    """
+    Add integers ``p`` and ``q`` modulo ``L``, where ``L`` is the order of
+    the main subgroup.
+
+    :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type p: bytes
+    :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type q: bytes
+    :return: an integer represented as a
+              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(p, bytes)
+        and isinstance(q, bytes)
+        and len(p) == crypto_core_ed25519_SCALARBYTES
+        and len(q) == crypto_core_ed25519_SCALARBYTES,
+        "Each integer must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
+
+    lib.crypto_core_ed25519_scalar_add(r, p, q)
+
+    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
+
+
+def crypto_core_ed25519_scalar_sub(p: bytes, q: bytes) -> bytes:
+    """
+    Subtract integers ``p`` and ``q`` modulo ``L``, where ``L`` is the
+    order of the main subgroup.
+
+    :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type p: bytes
+    :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type q: bytes
+    :return: an integer represented as a
+              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(p, bytes)
+        and isinstance(q, bytes)
+        and len(p) == crypto_core_ed25519_SCALARBYTES
+        and len(q) == crypto_core_ed25519_SCALARBYTES,
+        "Each integer must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
+
+    lib.crypto_core_ed25519_scalar_sub(r, p, q)
+
+    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
+
+
+def crypto_core_ed25519_scalar_mul(p: bytes, q: bytes) -> bytes:
+    """
+    Multiply integers ``p`` and ``q`` modulo ``L``, where ``L`` is the
+    order of the main subgroup.
+
+    :param p: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type p: bytes
+    :param q: a :py:data:`.crypto_core_ed25519_SCALARBYTES`
+              long bytes sequence representing an integer
+    :type q: bytes
+    :return: an integer represented as a
+              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(p, bytes)
+        and isinstance(q, bytes)
+        and len(p) == crypto_core_ed25519_SCALARBYTES
+        and len(q) == crypto_core_ed25519_SCALARBYTES,
+        "Each integer must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
+
+    lib.crypto_core_ed25519_scalar_mul(r, p, q)
+
+    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
+
+
+def crypto_core_ed25519_scalar_reduce(s: bytes) -> bytes:
+    """
+    Reduce integer ``s`` to ``s`` modulo ``L``, where ``L`` is the order
+    of the main subgroup.
+
+    :param s: a :py:data:`.crypto_core_ed25519_NONREDUCEDSCALARBYTES`
+              long bytes sequence representing an integer
+    :type s: bytes
+    :return: an integer represented as a
+              :py:data:`.crypto_core_ed25519_SCALARBYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_core_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(s, bytes)
+        and len(s) == crypto_core_ed25519_NONREDUCEDSCALARBYTES,
+        "Integer s must be a {} long bytes sequence".format(
+            "crypto_core_ed25519_NONREDUCEDSCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    r = ffi.new("unsigned char[]", crypto_core_ed25519_SCALARBYTES)
+
+    lib.crypto_core_ed25519_scalar_reduce(r, s)
+
+    return ffi.buffer(r, crypto_core_ed25519_SCALARBYTES)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_generichash.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_generichash.py
new file mode 100644
index 00000000..6ab385a5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_generichash.py
@@ -0,0 +1,281 @@
+# Copyright 2013-2019 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import NoReturn, TypeVar
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+crypto_generichash_BYTES: int = lib.crypto_generichash_blake2b_bytes()
+crypto_generichash_BYTES_MIN: int = lib.crypto_generichash_blake2b_bytes_min()
+crypto_generichash_BYTES_MAX: int = lib.crypto_generichash_blake2b_bytes_max()
+crypto_generichash_KEYBYTES: int = lib.crypto_generichash_blake2b_keybytes()
+crypto_generichash_KEYBYTES_MIN: int = (
+    lib.crypto_generichash_blake2b_keybytes_min()
+)
+crypto_generichash_KEYBYTES_MAX: int = (
+    lib.crypto_generichash_blake2b_keybytes_max()
+)
+crypto_generichash_SALTBYTES: int = lib.crypto_generichash_blake2b_saltbytes()
+crypto_generichash_PERSONALBYTES: int = (
+    lib.crypto_generichash_blake2b_personalbytes()
+)
+crypto_generichash_STATEBYTES: int = lib.crypto_generichash_statebytes()
+
+_OVERLONG = "{0} length greater than {1} bytes"
+_TOOBIG = "{0} greater than {1}"
+
+
+def _checkparams(
+    digest_size: int, key: bytes, salt: bytes, person: bytes
+) -> None:
+    """Check hash parameters"""
+    ensure(
+        isinstance(key, bytes),
+        "Key must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(salt, bytes),
+        "Salt must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(person, bytes),
+        "Person must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(digest_size, int),
+        "Digest size must be an integer number",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        digest_size <= crypto_generichash_BYTES_MAX,
+        _TOOBIG.format("Digest_size", crypto_generichash_BYTES_MAX),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        len(key) <= crypto_generichash_KEYBYTES_MAX,
+        _OVERLONG.format("Key", crypto_generichash_KEYBYTES_MAX),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        len(salt) <= crypto_generichash_SALTBYTES,
+        _OVERLONG.format("Salt", crypto_generichash_SALTBYTES),
+        raising=exc.ValueError,
+    )
+
+    ensure(
+        len(person) <= crypto_generichash_PERSONALBYTES,
+        _OVERLONG.format("Person", crypto_generichash_PERSONALBYTES),
+        raising=exc.ValueError,
+    )
+
+
+def generichash_blake2b_salt_personal(
+    data: bytes,
+    digest_size: int = crypto_generichash_BYTES,
+    key: bytes = b"",
+    salt: bytes = b"",
+    person: bytes = b"",
+) -> bytes:
+    """One shot hash interface
+
+    :param data: the input data to the hash function
+    :type data: bytes
+    :param digest_size: must be at most
+                        :py:data:`.crypto_generichash_BYTES_MAX`;
+                        the default digest size is
+                        :py:data:`.crypto_generichash_BYTES`
+    :type digest_size: int
+    :param key: must be at most
+                :py:data:`.crypto_generichash_KEYBYTES_MAX` long
+    :type key: bytes
+    :param salt: must be at most
+                 :py:data:`.crypto_generichash_SALTBYTES` long;
+                 will be zero-padded if needed
+    :type salt: bytes
+    :param person: must be at most
+                   :py:data:`.crypto_generichash_PERSONALBYTES` long:
+                   will be zero-padded if needed
+    :type person: bytes
+    :return: digest_size long digest
+    :rtype: bytes
+    """
+
+    _checkparams(digest_size, key, salt, person)
+
+    ensure(
+        isinstance(data, bytes),
+        "Input data must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+
+    digest = ffi.new("unsigned char[]", digest_size)
+
+    # both _salt and _personal must be zero-padded to the correct length
+    _salt = ffi.new("unsigned char []", crypto_generichash_SALTBYTES)
+    _person = ffi.new("unsigned char []", crypto_generichash_PERSONALBYTES)
+
+    ffi.memmove(_salt, salt, len(salt))
+    ffi.memmove(_person, person, len(person))
+
+    rc = lib.crypto_generichash_blake2b_salt_personal(
+        digest, digest_size, data, len(data), key, len(key), _salt, _person
+    )
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+
+    return ffi.buffer(digest, digest_size)[:]
+
+
+_Blake2State = TypeVar("_Blake2State", bound="Blake2State")
+
+
+class Blake2State:
+    """
+    Python-level wrapper for the crypto_generichash_blake2b state buffer
+    """
+
+    __slots__ = ["_statebuf", "digest_size"]
+
+    def __init__(self, digest_size: int):
+        self._statebuf = ffi.new(
+            "unsigned char[]", crypto_generichash_STATEBYTES
+        )
+        self.digest_size = digest_size
+
+    def __reduce__(self) -> NoReturn:
+        """
+        Raise the same exception as hashlib's blake implementation
+        on copy.copy()
+        """
+        raise TypeError(
+            "can't pickle {} objects".format(self.__class__.__name__)
+        )
+
+    def copy(self: _Blake2State) -> _Blake2State:
+        _st = self.__class__(self.digest_size)
+        ffi.memmove(
+            _st._statebuf, self._statebuf, crypto_generichash_STATEBYTES
+        )
+        return _st
+
+
+def generichash_blake2b_init(
+    key: bytes = b"",
+    salt: bytes = b"",
+    person: bytes = b"",
+    digest_size: int = crypto_generichash_BYTES,
+) -> Blake2State:
+    """
+    Create a new initialized blake2b hash state
+
+    :param key: must be at most
+                :py:data:`.crypto_generichash_KEYBYTES_MAX` long
+    :type key: bytes
+    :param salt: must be at most
+                 :py:data:`.crypto_generichash_SALTBYTES` long;
+                 will be zero-padded if needed
+    :type salt: bytes
+    :param person: must be at most
+                   :py:data:`.crypto_generichash_PERSONALBYTES` long:
+                   will be zero-padded if needed
+    :type person: bytes
+    :param digest_size: must be at most
+                        :py:data:`.crypto_generichash_BYTES_MAX`;
+                        the default digest size is
+                        :py:data:`.crypto_generichash_BYTES`
+    :type digest_size: int
+    :return: a initialized :py:class:`.Blake2State`
+    :rtype: object
+    """
+
+    _checkparams(digest_size, key, salt, person)
+
+    state = Blake2State(digest_size)
+
+    # both _salt and _personal must be zero-padded to the correct length
+    _salt = ffi.new("unsigned char []", crypto_generichash_SALTBYTES)
+    _person = ffi.new("unsigned char []", crypto_generichash_PERSONALBYTES)
+
+    ffi.memmove(_salt, salt, len(salt))
+    ffi.memmove(_person, person, len(person))
+
+    rc = lib.crypto_generichash_blake2b_init_salt_personal(
+        state._statebuf, key, len(key), digest_size, _salt, _person
+    )
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+
+    return state
+
+
+def generichash_blake2b_update(state: Blake2State, data: bytes) -> None:
+    """Update the blake2b hash state
+
+    :param state: a initialized Blake2bState object as returned from
+                     :py:func:`.crypto_generichash_blake2b_init`
+    :type state: :py:class:`.Blake2State`
+    :param data:
+    :type data: bytes
+    """
+
+    ensure(
+        isinstance(state, Blake2State),
+        "State must be a Blake2State object",
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(data, bytes),
+        "Input data must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+
+    rc = lib.crypto_generichash_blake2b_update(
+        state._statebuf, data, len(data)
+    )
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+
+
+def generichash_blake2b_final(state: Blake2State) -> bytes:
+    """Finalize the blake2b hash state and return the digest.
+
+    :param state: a initialized Blake2bState object as returned from
+                     :py:func:`.crypto_generichash_blake2b_init`
+    :type state: :py:class:`.Blake2State`
+    :return: the blake2 digest of the passed-in data stream
+    :rtype: bytes
+    """
+
+    ensure(
+        isinstance(state, Blake2State),
+        "State must be a Blake2State object",
+        raising=exc.TypeError,
+    )
+
+    _digest = ffi.new("unsigned char[]", crypto_generichash_BYTES_MAX)
+    rc = lib.crypto_generichash_blake2b_final(
+        state._statebuf, _digest, state.digest_size
+    )
+
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+    return ffi.buffer(_digest, state.digest_size)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_hash.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_hash.py
new file mode 100644
index 00000000..2bab3991
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_hash.py
@@ -0,0 +1,63 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+# crypto_hash_BYTES = lib.crypto_hash_bytes()
+crypto_hash_BYTES: int = lib.crypto_hash_sha512_bytes()
+crypto_hash_sha256_BYTES: int = lib.crypto_hash_sha256_bytes()
+crypto_hash_sha512_BYTES: int = lib.crypto_hash_sha512_bytes()
+
+
+def crypto_hash(message: bytes) -> bytes:
+    """
+    Hashes and returns the message ``message``.
+
+    :param message: bytes
+    :rtype: bytes
+    """
+    digest = ffi.new("unsigned char[]", crypto_hash_BYTES)
+    rc = lib.crypto_hash(digest, message, len(message))
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+    return ffi.buffer(digest, crypto_hash_BYTES)[:]
+
+
+def crypto_hash_sha256(message: bytes) -> bytes:
+    """
+    Hashes and returns the message ``message``.
+
+    :param message: bytes
+    :rtype: bytes
+    """
+    digest = ffi.new("unsigned char[]", crypto_hash_sha256_BYTES)
+    rc = lib.crypto_hash_sha256(digest, message, len(message))
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+    return ffi.buffer(digest, crypto_hash_sha256_BYTES)[:]
+
+
+def crypto_hash_sha512(message: bytes) -> bytes:
+    """
+    Hashes and returns the message ``message``.
+
+    :param message: bytes
+    :rtype: bytes
+    """
+    digest = ffi.new("unsigned char[]", crypto_hash_sha512_BYTES)
+    rc = lib.crypto_hash_sha512(digest, message, len(message))
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+    return ffi.buffer(digest, crypto_hash_sha512_BYTES)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_kx.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_kx.py
new file mode 100644
index 00000000..172a19f8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_kx.py
@@ -0,0 +1,200 @@
+# Copyright 2018 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import Tuple
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+__all__ = [
+    "crypto_kx_keypair",
+    "crypto_kx_client_session_keys",
+    "crypto_kx_server_session_keys",
+    "crypto_kx_PUBLIC_KEY_BYTES",
+    "crypto_kx_SECRET_KEY_BYTES",
+    "crypto_kx_SEED_BYTES",
+    "crypto_kx_SESSION_KEY_BYTES",
+]
+
+"""
+Implementations of client, server key exchange
+"""
+crypto_kx_PUBLIC_KEY_BYTES: int = lib.crypto_kx_publickeybytes()
+crypto_kx_SECRET_KEY_BYTES: int = lib.crypto_kx_secretkeybytes()
+crypto_kx_SEED_BYTES: int = lib.crypto_kx_seedbytes()
+crypto_kx_SESSION_KEY_BYTES: int = lib.crypto_kx_sessionkeybytes()
+
+
+def crypto_kx_keypair() -> Tuple[bytes, bytes]:
+    """
+    Generate a keypair.
+    This is a duplicate crypto_box_keypair, but
+    is included for api consistency.
+    :return: (public_key, secret_key)
+    :rtype: (bytes, bytes)
+    """
+    public_key = ffi.new("unsigned char[]", crypto_kx_PUBLIC_KEY_BYTES)
+    secret_key = ffi.new("unsigned char[]", crypto_kx_SECRET_KEY_BYTES)
+    res = lib.crypto_kx_keypair(public_key, secret_key)
+    ensure(res == 0, "Key generation failed.", raising=exc.CryptoError)
+
+    return (
+        ffi.buffer(public_key, crypto_kx_PUBLIC_KEY_BYTES)[:],
+        ffi.buffer(secret_key, crypto_kx_SECRET_KEY_BYTES)[:],
+    )
+
+
+def crypto_kx_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
+    """
+    Generate a keypair with a given seed.
+    This is functionally the same as crypto_box_seed_keypair, however
+    it uses the blake2b hash primitive instead of sha512.
+    It is included mainly for api consistency when using crypto_kx.
+    :param seed: random seed
+    :type seed: bytes
+    :return: (public_key, secret_key)
+    :rtype: (bytes, bytes)
+    """
+    public_key = ffi.new("unsigned char[]", crypto_kx_PUBLIC_KEY_BYTES)
+    secret_key = ffi.new("unsigned char[]", crypto_kx_SECRET_KEY_BYTES)
+    ensure(
+        isinstance(seed, bytes) and len(seed) == crypto_kx_SEED_BYTES,
+        "Seed must be a {} byte long bytes sequence".format(
+            crypto_kx_SEED_BYTES
+        ),
+        raising=exc.TypeError,
+    )
+    res = lib.crypto_kx_seed_keypair(public_key, secret_key, seed)
+    ensure(res == 0, "Key generation failed.", raising=exc.CryptoError)
+
+    return (
+        ffi.buffer(public_key, crypto_kx_PUBLIC_KEY_BYTES)[:],
+        ffi.buffer(secret_key, crypto_kx_SECRET_KEY_BYTES)[:],
+    )
+
+
+def crypto_kx_client_session_keys(
+    client_public_key: bytes,
+    client_secret_key: bytes,
+    server_public_key: bytes,
+) -> Tuple[bytes, bytes]:
+    """
+    Generate session keys for the client.
+    :param client_public_key:
+    :type client_public_key: bytes
+    :param client_secret_key:
+    :type client_secret_key: bytes
+    :param server_public_key:
+    :type server_public_key: bytes
+    :return: (rx_key, tx_key)
+    :rtype: (bytes, bytes)
+    """
+    ensure(
+        isinstance(client_public_key, bytes)
+        and len(client_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
+        "Client public key must be a {} bytes long bytes sequence".format(
+            crypto_kx_PUBLIC_KEY_BYTES
+        ),
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(client_secret_key, bytes)
+        and len(client_secret_key) == crypto_kx_SECRET_KEY_BYTES,
+        "Client secret key must be a {} bytes long bytes sequence".format(
+            crypto_kx_PUBLIC_KEY_BYTES
+        ),
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(server_public_key, bytes)
+        and len(server_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
+        "Server public key must be a {} bytes long bytes sequence".format(
+            crypto_kx_PUBLIC_KEY_BYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    rx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
+    tx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
+    res = lib.crypto_kx_client_session_keys(
+        rx_key, tx_key, client_public_key, client_secret_key, server_public_key
+    )
+    ensure(
+        res == 0,
+        "Client session key generation failed.",
+        raising=exc.CryptoError,
+    )
+
+    return (
+        ffi.buffer(rx_key, crypto_kx_SESSION_KEY_BYTES)[:],
+        ffi.buffer(tx_key, crypto_kx_SESSION_KEY_BYTES)[:],
+    )
+
+
+def crypto_kx_server_session_keys(
+    server_public_key: bytes,
+    server_secret_key: bytes,
+    client_public_key: bytes,
+) -> Tuple[bytes, bytes]:
+    """
+    Generate session keys for the server.
+    :param server_public_key:
+    :type server_public_key: bytes
+    :param server_secret_key:
+    :type server_secret_key: bytes
+    :param client_public_key:
+    :type client_public_key: bytes
+    :return: (rx_key, tx_key)
+    :rtype: (bytes, bytes)
+    """
+    ensure(
+        isinstance(server_public_key, bytes)
+        and len(server_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
+        "Server public key must be a {} bytes long bytes sequence".format(
+            crypto_kx_PUBLIC_KEY_BYTES
+        ),
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(server_secret_key, bytes)
+        and len(server_secret_key) == crypto_kx_SECRET_KEY_BYTES,
+        "Server secret key must be a {} bytes long bytes sequence".format(
+            crypto_kx_PUBLIC_KEY_BYTES
+        ),
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(client_public_key, bytes)
+        and len(client_public_key) == crypto_kx_PUBLIC_KEY_BYTES,
+        "Client public key must be a {} bytes long bytes sequence".format(
+            crypto_kx_PUBLIC_KEY_BYTES
+        ),
+        raising=exc.TypeError,
+    )
+
+    rx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
+    tx_key = ffi.new("unsigned char[]", crypto_kx_SESSION_KEY_BYTES)
+    res = lib.crypto_kx_server_session_keys(
+        rx_key, tx_key, server_public_key, server_secret_key, client_public_key
+    )
+    ensure(
+        res == 0,
+        "Server session key generation failed.",
+        raising=exc.CryptoError,
+    )
+
+    return (
+        ffi.buffer(rx_key, crypto_kx_SESSION_KEY_BYTES)[:],
+        ffi.buffer(tx_key, crypto_kx_SESSION_KEY_BYTES)[:],
+    )
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_pwhash.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_pwhash.py
new file mode 100644
index 00000000..0c4cc1a2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_pwhash.py
@@ -0,0 +1,600 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import sys
+from typing import Tuple
+
+import nacl.exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+has_crypto_pwhash_scryptsalsa208sha256 = bool(
+    lib.PYNACL_HAS_CRYPTO_PWHASH_SCRYPTSALSA208SHA256
+)
+
+crypto_pwhash_scryptsalsa208sha256_STRPREFIX = b""
+crypto_pwhash_scryptsalsa208sha256_SALTBYTES = 0
+crypto_pwhash_scryptsalsa208sha256_STRBYTES = 0
+crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = 0
+crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = 0
+crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = 0
+crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = 0
+crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = 0
+crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = 0
+crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = 0
+crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = 0
+crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = 0
+crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = 0
+crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = 0
+crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = 0
+
+if has_crypto_pwhash_scryptsalsa208sha256:
+    crypto_pwhash_scryptsalsa208sha256_STRPREFIX = ffi.string(
+        ffi.cast("char *", lib.crypto_pwhash_scryptsalsa208sha256_strprefix())
+    )[:]
+    crypto_pwhash_scryptsalsa208sha256_SALTBYTES = (
+        lib.crypto_pwhash_scryptsalsa208sha256_saltbytes()
+    )
+    crypto_pwhash_scryptsalsa208sha256_STRBYTES = (
+        lib.crypto_pwhash_scryptsalsa208sha256_strbytes()
+    )
+    crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = (
+        lib.crypto_pwhash_scryptsalsa208sha256_passwd_min()
+    )
+    crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = (
+        lib.crypto_pwhash_scryptsalsa208sha256_passwd_max()
+    )
+    crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = (
+        lib.crypto_pwhash_scryptsalsa208sha256_bytes_min()
+    )
+    crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = (
+        lib.crypto_pwhash_scryptsalsa208sha256_bytes_max()
+    )
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = (
+        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_min()
+    )
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = (
+        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_max()
+    )
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = (
+        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_min()
+    )
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = (
+        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_max()
+    )
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = (
+        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_interactive()
+    )
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = (
+        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_interactive()
+    )
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = (
+        lib.crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive()
+    )
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = (
+        lib.crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive()
+    )
+
+crypto_pwhash_ALG_ARGON2I13: int = lib.crypto_pwhash_alg_argon2i13()
+crypto_pwhash_ALG_ARGON2ID13: int = lib.crypto_pwhash_alg_argon2id13()
+crypto_pwhash_ALG_DEFAULT: int = lib.crypto_pwhash_alg_default()
+
+crypto_pwhash_SALTBYTES: int = lib.crypto_pwhash_saltbytes()
+crypto_pwhash_STRBYTES: int = lib.crypto_pwhash_strbytes()
+
+crypto_pwhash_PASSWD_MIN: int = lib.crypto_pwhash_passwd_min()
+crypto_pwhash_PASSWD_MAX: int = lib.crypto_pwhash_passwd_max()
+crypto_pwhash_BYTES_MIN: int = lib.crypto_pwhash_bytes_min()
+crypto_pwhash_BYTES_MAX: int = lib.crypto_pwhash_bytes_max()
+
+crypto_pwhash_argon2i_STRPREFIX: bytes = ffi.string(
+    ffi.cast("char *", lib.crypto_pwhash_argon2i_strprefix())
+)[:]
+crypto_pwhash_argon2i_MEMLIMIT_MIN: int = (
+    lib.crypto_pwhash_argon2i_memlimit_min()
+)
+crypto_pwhash_argon2i_MEMLIMIT_MAX: int = (
+    lib.crypto_pwhash_argon2i_memlimit_max()
+)
+crypto_pwhash_argon2i_OPSLIMIT_MIN: int = (
+    lib.crypto_pwhash_argon2i_opslimit_min()
+)
+crypto_pwhash_argon2i_OPSLIMIT_MAX: int = (
+    lib.crypto_pwhash_argon2i_opslimit_max()
+)
+crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE: int = (
+    lib.crypto_pwhash_argon2i_opslimit_interactive()
+)
+crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE: int = (
+    lib.crypto_pwhash_argon2i_memlimit_interactive()
+)
+crypto_pwhash_argon2i_OPSLIMIT_MODERATE: int = (
+    lib.crypto_pwhash_argon2i_opslimit_moderate()
+)
+crypto_pwhash_argon2i_MEMLIMIT_MODERATE: int = (
+    lib.crypto_pwhash_argon2i_memlimit_moderate()
+)
+crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE: int = (
+    lib.crypto_pwhash_argon2i_opslimit_sensitive()
+)
+crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE: int = (
+    lib.crypto_pwhash_argon2i_memlimit_sensitive()
+)
+
+crypto_pwhash_argon2id_STRPREFIX: bytes = ffi.string(
+    ffi.cast("char *", lib.crypto_pwhash_argon2id_strprefix())
+)[:]
+crypto_pwhash_argon2id_MEMLIMIT_MIN: int = (
+    lib.crypto_pwhash_argon2id_memlimit_min()
+)
+crypto_pwhash_argon2id_MEMLIMIT_MAX: int = (
+    lib.crypto_pwhash_argon2id_memlimit_max()
+)
+crypto_pwhash_argon2id_OPSLIMIT_MIN: int = (
+    lib.crypto_pwhash_argon2id_opslimit_min()
+)
+crypto_pwhash_argon2id_OPSLIMIT_MAX: int = (
+    lib.crypto_pwhash_argon2id_opslimit_max()
+)
+crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE: int = (
+    lib.crypto_pwhash_argon2id_opslimit_interactive()
+)
+crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE: int = (
+    lib.crypto_pwhash_argon2id_memlimit_interactive()
+)
+crypto_pwhash_argon2id_OPSLIMIT_MODERATE: int = (
+    lib.crypto_pwhash_argon2id_opslimit_moderate()
+)
+crypto_pwhash_argon2id_MEMLIMIT_MODERATE: int = (
+    lib.crypto_pwhash_argon2id_memlimit_moderate()
+)
+crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE: int = (
+    lib.crypto_pwhash_argon2id_opslimit_sensitive()
+)
+crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE: int = (
+    lib.crypto_pwhash_argon2id_memlimit_sensitive()
+)
+
+SCRYPT_OPSLIMIT_INTERACTIVE = (
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
+)
+SCRYPT_MEMLIMIT_INTERACTIVE = (
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
+)
+SCRYPT_OPSLIMIT_SENSITIVE = (
+    crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE
+)
+SCRYPT_MEMLIMIT_SENSITIVE = (
+    crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE
+)
+SCRYPT_SALTBYTES = crypto_pwhash_scryptsalsa208sha256_SALTBYTES
+SCRYPT_STRBYTES = crypto_pwhash_scryptsalsa208sha256_STRBYTES
+
+SCRYPT_PR_MAX = (1 << 30) - 1
+LOG2_UINT64_MAX = 63
+UINT64_MAX = (1 << 64) - 1
+SCRYPT_MAX_MEM = 32 * (1024 * 1024)
+
+
+def _check_memory_occupation(
+    n: int, r: int, p: int, maxmem: int = SCRYPT_MAX_MEM
+) -> None:
+    ensure(r != 0, "Invalid block size", raising=exc.ValueError)
+
+    ensure(p != 0, "Invalid parallelization factor", raising=exc.ValueError)
+
+    ensure(
+        (n & (n - 1)) == 0,
+        "Cost factor must be a power of 2",
+        raising=exc.ValueError,
+    )
+
+    ensure(n > 1, "Cost factor must be at least 2", raising=exc.ValueError)
+
+    ensure(
+        p <= SCRYPT_PR_MAX / r,
+        "p*r is greater than {}".format(SCRYPT_PR_MAX),
+        raising=exc.ValueError,
+    )
+
+    ensure(n < (1 << (16 * r)), raising=exc.ValueError)
+
+    Blen = p * 128 * r
+
+    i = UINT64_MAX / 128
+
+    ensure(n + 2 <= i / r, raising=exc.ValueError)
+
+    Vlen = 32 * r * (n + 2) * 4
+
+    ensure(Blen <= UINT64_MAX - Vlen, raising=exc.ValueError)
+
+    ensure(Blen <= sys.maxsize - Vlen, raising=exc.ValueError)
+
+    ensure(
+        Blen + Vlen <= maxmem,
+        "Memory limit would be exceeded with the choosen n, r, p",
+        raising=exc.ValueError,
+    )
+
+
+def nacl_bindings_pick_scrypt_params(
+    opslimit: int, memlimit: int
+) -> Tuple[int, int, int]:
+    """Python implementation of libsodium's pickparams"""
+
+    if opslimit < 32768:
+        opslimit = 32768
+
+    r = 8
+
+    if opslimit < (memlimit // 32):
+        p = 1
+        maxn = opslimit // (4 * r)
+        for n_log2 in range(1, 63):  # pragma: no branch
+            if (2 ** n_log2) > (maxn // 2):
+                break
+    else:
+        maxn = memlimit // (r * 128)
+        for n_log2 in range(1, 63):  # pragma: no branch
+            if (2 ** n_log2) > maxn // 2:
+                break
+
+        maxrp = (opslimit // 4) // (2 ** n_log2)
+
+        if maxrp > 0x3FFFFFFF:  # pragma: no cover
+            maxrp = 0x3FFFFFFF
+
+        p = maxrp // r
+
+    return n_log2, r, p
+
+
+def crypto_pwhash_scryptsalsa208sha256_ll(
+    passwd: bytes,
+    salt: bytes,
+    n: int,
+    r: int,
+    p: int,
+    dklen: int = 64,
+    maxmem: int = SCRYPT_MAX_MEM,
+) -> bytes:
+    """
+    Derive a cryptographic key using the ``passwd`` and ``salt``
+    given as input.
+
+    The work factor can be tuned by by picking different
+    values for the parameters
+
+    :param bytes passwd:
+    :param bytes salt:
+    :param bytes salt: *must* be *exactly* :py:const:`.SALTBYTES` long
+    :param int dklen:
+    :param int opslimit:
+    :param int n:
+    :param int r: block size,
+    :param int p: the parallelism factor
+    :param int maxmem: the maximum available memory available for scrypt's
+                       operations
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_pwhash_scryptsalsa208sha256,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(isinstance(n, int), raising=TypeError)
+    ensure(isinstance(r, int), raising=TypeError)
+    ensure(isinstance(p, int), raising=TypeError)
+
+    ensure(isinstance(passwd, bytes), raising=TypeError)
+    ensure(isinstance(salt, bytes), raising=TypeError)
+
+    _check_memory_occupation(n, r, p, maxmem)
+
+    buf = ffi.new("uint8_t[]", dklen)
+
+    ret = lib.crypto_pwhash_scryptsalsa208sha256_ll(
+        passwd, len(passwd), salt, len(salt), n, r, p, buf, dklen
+    )
+
+    ensure(
+        ret == 0,
+        "Unexpected failure in key derivation",
+        raising=exc.RuntimeError,
+    )
+
+    return ffi.buffer(ffi.cast("char *", buf), dklen)[:]
+
+
+def crypto_pwhash_scryptsalsa208sha256_str(
+    passwd: bytes,
+    opslimit: int = SCRYPT_OPSLIMIT_INTERACTIVE,
+    memlimit: int = SCRYPT_MEMLIMIT_INTERACTIVE,
+) -> bytes:
+    """
+    Derive a cryptographic key using the ``passwd`` and ``salt``
+    given as input, returning a string representation which includes
+    the salt and the tuning parameters.
+
+    The returned string can be directly stored as a password hash.
+
+    See :py:func:`.crypto_pwhash_scryptsalsa208sha256` for a short
+    discussion about ``opslimit`` and ``memlimit`` values.
+
+    :param bytes passwd:
+    :param int opslimit:
+    :param int memlimit:
+    :return: serialized key hash, including salt and tuning parameters
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_pwhash_scryptsalsa208sha256,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    buf = ffi.new("char[]", SCRYPT_STRBYTES)
+
+    ret = lib.crypto_pwhash_scryptsalsa208sha256_str(
+        buf, passwd, len(passwd), opslimit, memlimit
+    )
+
+    ensure(
+        ret == 0,
+        "Unexpected failure in password hashing",
+        raising=exc.RuntimeError,
+    )
+
+    return ffi.string(buf)
+
+
+def crypto_pwhash_scryptsalsa208sha256_str_verify(
+    passwd_hash: bytes, passwd: bytes
+) -> bool:
+    """
+    Verifies the ``passwd`` against the ``passwd_hash`` that was generated.
+    Returns True or False depending on the success
+
+    :param passwd_hash: bytes
+    :param passwd: bytes
+    :rtype: boolean
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_pwhash_scryptsalsa208sha256,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        len(passwd_hash) == SCRYPT_STRBYTES - 1,
+        "Invalid password hash",
+        raising=exc.ValueError,
+    )
+
+    ret = lib.crypto_pwhash_scryptsalsa208sha256_str_verify(
+        passwd_hash, passwd, len(passwd)
+    )
+    ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
+    # all went well, therefore:
+    return True
+
+
+def _check_argon2_limits_alg(opslimit: int, memlimit: int, alg: int) -> None:
+
+    if alg == crypto_pwhash_ALG_ARGON2I13:
+        if memlimit < crypto_pwhash_argon2i_MEMLIMIT_MIN:
+            raise exc.ValueError(
+                "memlimit must be at least {} bytes".format(
+                    crypto_pwhash_argon2i_MEMLIMIT_MIN
+                )
+            )
+        elif memlimit > crypto_pwhash_argon2i_MEMLIMIT_MAX:
+            raise exc.ValueError(
+                "memlimit must be at most {} bytes".format(
+                    crypto_pwhash_argon2i_MEMLIMIT_MAX
+                )
+            )
+        if opslimit < crypto_pwhash_argon2i_OPSLIMIT_MIN:
+            raise exc.ValueError(
+                "opslimit must be at least {}".format(
+                    crypto_pwhash_argon2i_OPSLIMIT_MIN
+                )
+            )
+        elif opslimit > crypto_pwhash_argon2i_OPSLIMIT_MAX:
+            raise exc.ValueError(
+                "opslimit must be at most {}".format(
+                    crypto_pwhash_argon2i_OPSLIMIT_MAX
+                )
+            )
+
+    elif alg == crypto_pwhash_ALG_ARGON2ID13:
+        if memlimit < crypto_pwhash_argon2id_MEMLIMIT_MIN:
+            raise exc.ValueError(
+                "memlimit must be at least {} bytes".format(
+                    crypto_pwhash_argon2id_MEMLIMIT_MIN
+                )
+            )
+        elif memlimit > crypto_pwhash_argon2id_MEMLIMIT_MAX:
+            raise exc.ValueError(
+                "memlimit must be at most {} bytes".format(
+                    crypto_pwhash_argon2id_MEMLIMIT_MAX
+                )
+            )
+        if opslimit < crypto_pwhash_argon2id_OPSLIMIT_MIN:
+            raise exc.ValueError(
+                "opslimit must be at least {}".format(
+                    crypto_pwhash_argon2id_OPSLIMIT_MIN
+                )
+            )
+        elif opslimit > crypto_pwhash_argon2id_OPSLIMIT_MAX:
+            raise exc.ValueError(
+                "opslimit must be at most {}".format(
+                    crypto_pwhash_argon2id_OPSLIMIT_MAX
+                )
+            )
+    else:
+        raise exc.TypeError("Unsupported algorithm")
+
+
+def crypto_pwhash_alg(
+    outlen: int,
+    passwd: bytes,
+    salt: bytes,
+    opslimit: int,
+    memlimit: int,
+    alg: int,
+) -> bytes:
+    """
+    Derive a raw cryptographic key using the ``passwd`` and the ``salt``
+    given as input to the ``alg`` algorithm.
+
+    :param outlen: the length of the derived key
+    :type outlen: int
+    :param passwd: The input password
+    :type passwd: bytes
+    :param salt:
+    :type salt: bytes
+    :param opslimit: computational cost
+    :type opslimit: int
+    :param memlimit: memory cost
+    :type memlimit: int
+    :param alg: algorithm identifier
+    :type alg: int
+    :return: derived key
+    :rtype: bytes
+    """
+    ensure(isinstance(outlen, int), raising=exc.TypeError)
+    ensure(isinstance(opslimit, int), raising=exc.TypeError)
+    ensure(isinstance(memlimit, int), raising=exc.TypeError)
+    ensure(isinstance(alg, int), raising=exc.TypeError)
+    ensure(isinstance(passwd, bytes), raising=exc.TypeError)
+
+    if len(salt) != crypto_pwhash_SALTBYTES:
+        raise exc.ValueError(
+            "salt must be exactly {} bytes long".format(
+                crypto_pwhash_SALTBYTES
+            )
+        )
+
+    if outlen < crypto_pwhash_BYTES_MIN:
+        raise exc.ValueError(
+            "derived key must be at least {} bytes long".format(
+                crypto_pwhash_BYTES_MIN
+            )
+        )
+
+    elif outlen > crypto_pwhash_BYTES_MAX:
+        raise exc.ValueError(
+            "derived key must be at most {} bytes long".format(
+                crypto_pwhash_BYTES_MAX
+            )
+        )
+
+    _check_argon2_limits_alg(opslimit, memlimit, alg)
+
+    outbuf = ffi.new("unsigned char[]", outlen)
+
+    ret = lib.crypto_pwhash(
+        outbuf, outlen, passwd, len(passwd), salt, opslimit, memlimit, alg
+    )
+
+    ensure(
+        ret == 0,
+        "Unexpected failure in key derivation",
+        raising=exc.RuntimeError,
+    )
+
+    return ffi.buffer(outbuf, outlen)[:]
+
+
+def crypto_pwhash_str_alg(
+    passwd: bytes,
+    opslimit: int,
+    memlimit: int,
+    alg: int,
+) -> bytes:
+    """
+    Derive a cryptographic key using the ``passwd`` given as input
+    and a random salt, returning a string representation which
+    includes the salt, the tuning parameters and the used algorithm.
+
+    :param passwd: The input password
+    :type passwd: bytes
+    :param opslimit: computational cost
+    :type opslimit: int
+    :param memlimit: memory cost
+    :type memlimit: int
+    :param alg: The algorithm to use
+    :type alg: int
+    :return: serialized derived key and parameters
+    :rtype: bytes
+    """
+    ensure(isinstance(opslimit, int), raising=TypeError)
+    ensure(isinstance(memlimit, int), raising=TypeError)
+    ensure(isinstance(passwd, bytes), raising=TypeError)
+
+    _check_argon2_limits_alg(opslimit, memlimit, alg)
+
+    outbuf = ffi.new("char[]", 128)
+
+    ret = lib.crypto_pwhash_str_alg(
+        outbuf, passwd, len(passwd), opslimit, memlimit, alg
+    )
+
+    ensure(
+        ret == 0,
+        "Unexpected failure in key derivation",
+        raising=exc.RuntimeError,
+    )
+
+    return ffi.string(outbuf)
+
+
+def crypto_pwhash_str_verify(passwd_hash: bytes, passwd: bytes) -> bool:
+    """
+    Verifies the ``passwd`` against a given password hash.
+
+    Returns True on success, raises InvalidkeyError on failure
+    :param passwd_hash: saved password hash
+    :type passwd_hash: bytes
+    :param passwd: password to be checked
+    :type passwd: bytes
+    :return: success
+    :rtype: boolean
+    """
+    ensure(isinstance(passwd_hash, bytes), raising=TypeError)
+    ensure(isinstance(passwd, bytes), raising=TypeError)
+    ensure(
+        len(passwd_hash) <= 127,
+        "Hash must be at most 127 bytes long",
+        raising=exc.ValueError,
+    )
+
+    ret = lib.crypto_pwhash_str_verify(passwd_hash, passwd, len(passwd))
+
+    ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
+    # all went well, therefore:
+    return True
+
+
+crypto_pwhash_argon2i_str_verify = crypto_pwhash_str_verify
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_scalarmult.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_scalarmult.py
new file mode 100644
index 00000000..ca4a2819
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_scalarmult.py
@@ -0,0 +1,240 @@
+# Copyright 2013-2018 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+has_crypto_scalarmult_ed25519 = bool(lib.PYNACL_HAS_CRYPTO_SCALARMULT_ED25519)
+
+crypto_scalarmult_BYTES: int = lib.crypto_scalarmult_bytes()
+crypto_scalarmult_SCALARBYTES: int = lib.crypto_scalarmult_scalarbytes()
+
+crypto_scalarmult_ed25519_BYTES = 0
+crypto_scalarmult_ed25519_SCALARBYTES = 0
+
+if has_crypto_scalarmult_ed25519:
+    crypto_scalarmult_ed25519_BYTES = lib.crypto_scalarmult_ed25519_bytes()
+    crypto_scalarmult_ed25519_SCALARBYTES = (
+        lib.crypto_scalarmult_ed25519_scalarbytes()
+    )
+
+
+def crypto_scalarmult_base(n: bytes) -> bytes:
+    """
+    Computes and returns the scalar product of a standard group element and an
+    integer ``n``.
+
+    :param n: bytes
+    :rtype: bytes
+    """
+    q = ffi.new("unsigned char[]", crypto_scalarmult_BYTES)
+
+    rc = lib.crypto_scalarmult_base(q, n)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(q, crypto_scalarmult_SCALARBYTES)[:]
+
+
+def crypto_scalarmult(n: bytes, p: bytes) -> bytes:
+    """
+    Computes and returns the scalar product of the given group element and an
+    integer ``n``.
+
+    :param p: bytes
+    :param n: bytes
+    :rtype: bytes
+    """
+    q = ffi.new("unsigned char[]", crypto_scalarmult_BYTES)
+
+    rc = lib.crypto_scalarmult(q, n, p)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(q, crypto_scalarmult_SCALARBYTES)[:]
+
+
+def crypto_scalarmult_ed25519_base(n: bytes) -> bytes:
+    """
+    Computes and returns the scalar product of a standard group element and an
+    integer ``n`` on the edwards25519 curve.
+
+    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
+              sequence representing a scalar
+    :type n: bytes
+    :return: a point on the edwards25519 curve, represented as a
+             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_scalarmult_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(n, bytes)
+        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
+        "Input must be a {} long bytes sequence".format(
+            "crypto_scalarmult_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)
+
+    rc = lib.crypto_scalarmult_ed25519_base(q, n)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]
+
+
+def crypto_scalarmult_ed25519_base_noclamp(n: bytes) -> bytes:
+    """
+    Computes and returns the scalar product of a standard group element and an
+    integer ``n`` on the edwards25519 curve. The integer ``n`` is not clamped.
+
+    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
+              sequence representing a scalar
+    :type n: bytes
+    :return: a point on the edwards25519 curve, represented as a
+             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_scalarmult_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(n, bytes)
+        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
+        "Input must be a {} long bytes sequence".format(
+            "crypto_scalarmult_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)
+
+    rc = lib.crypto_scalarmult_ed25519_base_noclamp(q, n)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]
+
+
+def crypto_scalarmult_ed25519(n: bytes, p: bytes) -> bytes:
+    """
+    Computes and returns the scalar product of a *clamped* integer ``n``
+    and the given group element on the edwards25519 curve.
+    The scalar is clamped, as done in the public key generation case,
+    by setting to zero the bits in position [0, 1, 2, 255] and setting
+    to one the bit in position 254.
+
+    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
+              sequence representing a scalar
+    :type n: bytes
+    :param p: a :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
+              representing a point on the edwards25519 curve
+    :type p: bytes
+    :return: a point on the edwards25519 curve, represented as a
+             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_scalarmult_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(n, bytes)
+        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
+        "Input must be a {} long bytes sequence".format(
+            "crypto_scalarmult_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(p, bytes) and len(p) == crypto_scalarmult_ed25519_BYTES,
+        "Input must be a {} long bytes sequence".format(
+            "crypto_scalarmult_ed25519_BYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)
+
+    rc = lib.crypto_scalarmult_ed25519(q, n, p)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]
+
+
+def crypto_scalarmult_ed25519_noclamp(n: bytes, p: bytes) -> bytes:
+    """
+    Computes and returns the scalar product of an integer ``n``
+    and the given group element on the edwards25519 curve. The integer
+    ``n`` is not clamped.
+
+    :param n: a :py:data:`.crypto_scalarmult_ed25519_SCALARBYTES` long bytes
+              sequence representing a scalar
+    :type n: bytes
+    :param p: a :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
+              representing a point on the edwards25519 curve
+    :type p: bytes
+    :return: a point on the edwards25519 curve, represented as a
+             :py:data:`.crypto_scalarmult_ed25519_BYTES` long bytes sequence
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_scalarmult_ed25519,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        isinstance(n, bytes)
+        and len(n) == crypto_scalarmult_ed25519_SCALARBYTES,
+        "Input must be a {} long bytes sequence".format(
+            "crypto_scalarmult_ed25519_SCALARBYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    ensure(
+        isinstance(p, bytes) and len(p) == crypto_scalarmult_ed25519_BYTES,
+        "Input must be a {} long bytes sequence".format(
+            "crypto_scalarmult_ed25519_BYTES"
+        ),
+        raising=exc.TypeError,
+    )
+
+    q = ffi.new("unsigned char[]", crypto_scalarmult_ed25519_BYTES)
+
+    rc = lib.crypto_scalarmult_ed25519_noclamp(q, n, p)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(q, crypto_scalarmult_ed25519_BYTES)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretbox.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretbox.py
new file mode 100644
index 00000000..2a632a2b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretbox.py
@@ -0,0 +1,86 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+crypto_secretbox_KEYBYTES: int = lib.crypto_secretbox_keybytes()
+crypto_secretbox_NONCEBYTES: int = lib.crypto_secretbox_noncebytes()
+crypto_secretbox_ZEROBYTES: int = lib.crypto_secretbox_zerobytes()
+crypto_secretbox_BOXZEROBYTES: int = lib.crypto_secretbox_boxzerobytes()
+crypto_secretbox_MACBYTES: int = lib.crypto_secretbox_macbytes()
+crypto_secretbox_MESSAGEBYTES_MAX: int = (
+    lib.crypto_secretbox_messagebytes_max()
+)
+
+
+def crypto_secretbox(message: bytes, nonce: bytes, key: bytes) -> bytes:
+    """
+    Encrypts and returns the message ``message`` with the secret ``key`` and
+    the nonce ``nonce``.
+
+    :param message: bytes
+    :param nonce: bytes
+    :param key: bytes
+    :rtype: bytes
+    """
+    if len(key) != crypto_secretbox_KEYBYTES:
+        raise exc.ValueError("Invalid key")
+
+    if len(nonce) != crypto_secretbox_NONCEBYTES:
+        raise exc.ValueError("Invalid nonce")
+
+    padded = b"\x00" * crypto_secretbox_ZEROBYTES + message
+    ciphertext = ffi.new("unsigned char[]", len(padded))
+
+    res = lib.crypto_secretbox(ciphertext, padded, len(padded), nonce, key)
+    ensure(res == 0, "Encryption failed", raising=exc.CryptoError)
+
+    ciphertext = ffi.buffer(ciphertext, len(padded))
+    return ciphertext[crypto_secretbox_BOXZEROBYTES:]
+
+
+def crypto_secretbox_open(
+    ciphertext: bytes, nonce: bytes, key: bytes
+) -> bytes:
+    """
+    Decrypt and returns the encrypted message ``ciphertext`` with the secret
+    ``key`` and the nonce ``nonce``.
+
+    :param ciphertext: bytes
+    :param nonce: bytes
+    :param key: bytes
+    :rtype: bytes
+    """
+    if len(key) != crypto_secretbox_KEYBYTES:
+        raise exc.ValueError("Invalid key")
+
+    if len(nonce) != crypto_secretbox_NONCEBYTES:
+        raise exc.ValueError("Invalid nonce")
+
+    padded = b"\x00" * crypto_secretbox_BOXZEROBYTES + ciphertext
+    plaintext = ffi.new("unsigned char[]", len(padded))
+
+    res = lib.crypto_secretbox_open(plaintext, padded, len(padded), nonce, key)
+    ensure(
+        res == 0,
+        "Decryption failed. Ciphertext failed verification",
+        raising=exc.CryptoError,
+    )
+
+    plaintext = ffi.buffer(plaintext, len(padded))
+    return plaintext[crypto_secretbox_ZEROBYTES:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretstream.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretstream.py
new file mode 100644
index 00000000..d7c6725e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_secretstream.py
@@ -0,0 +1,357 @@
+# Copyright 2013-2018 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import ByteString, Optional, Tuple, cast
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+crypto_secretstream_xchacha20poly1305_ABYTES: int = (
+    lib.crypto_secretstream_xchacha20poly1305_abytes()
+)
+crypto_secretstream_xchacha20poly1305_HEADERBYTES: int = (
+    lib.crypto_secretstream_xchacha20poly1305_headerbytes()
+)
+crypto_secretstream_xchacha20poly1305_KEYBYTES: int = (
+    lib.crypto_secretstream_xchacha20poly1305_keybytes()
+)
+crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX: int = (
+    lib.crypto_secretstream_xchacha20poly1305_messagebytes_max()
+)
+crypto_secretstream_xchacha20poly1305_STATEBYTES: int = (
+    lib.crypto_secretstream_xchacha20poly1305_statebytes()
+)
+
+
+crypto_secretstream_xchacha20poly1305_TAG_MESSAGE: int = (
+    lib.crypto_secretstream_xchacha20poly1305_tag_message()
+)
+crypto_secretstream_xchacha20poly1305_TAG_PUSH: int = (
+    lib.crypto_secretstream_xchacha20poly1305_tag_push()
+)
+crypto_secretstream_xchacha20poly1305_TAG_REKEY: int = (
+    lib.crypto_secretstream_xchacha20poly1305_tag_rekey()
+)
+crypto_secretstream_xchacha20poly1305_TAG_FINAL: int = (
+    lib.crypto_secretstream_xchacha20poly1305_tag_final()
+)
+
+
+def crypto_secretstream_xchacha20poly1305_keygen() -> bytes:
+    """
+    Generate a key for use with
+    :func:`.crypto_secretstream_xchacha20poly1305_init_push`.
+
+    """
+    keybuf = ffi.new(
+        "unsigned char[]",
+        crypto_secretstream_xchacha20poly1305_KEYBYTES,
+    )
+    lib.crypto_secretstream_xchacha20poly1305_keygen(keybuf)
+    return ffi.buffer(keybuf)[:]
+
+
+class crypto_secretstream_xchacha20poly1305_state:
+    """
+    An object wrapping the crypto_secretstream_xchacha20poly1305 state.
+
+    """
+
+    __slots__ = ["statebuf", "rawbuf", "tagbuf"]
+
+    def __init__(self) -> None:
+        """Initialize a clean state object."""
+        self.statebuf: ByteString = ffi.new(
+            "unsigned char[]",
+            crypto_secretstream_xchacha20poly1305_STATEBYTES,
+        )
+
+        self.rawbuf: Optional[ByteString] = None
+        self.tagbuf: Optional[ByteString] = None
+
+
+def crypto_secretstream_xchacha20poly1305_init_push(
+    state: crypto_secretstream_xchacha20poly1305_state, key: bytes
+) -> bytes:
+    """
+    Initialize a crypto_secretstream_xchacha20poly1305 encryption buffer.
+
+    :param state: a secretstream state object
+    :type state: crypto_secretstream_xchacha20poly1305_state
+    :param key: must be
+                :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
+    :type key: bytes
+    :return: header
+    :rtype: bytes
+
+    """
+    ensure(
+        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
+        "State must be a crypto_secretstream_xchacha20poly1305_state object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(key, bytes),
+        "Key must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+    ensure(
+        len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
+        "Invalid key length",
+        raising=exc.ValueError,
+    )
+
+    headerbuf = ffi.new(
+        "unsigned char []",
+        crypto_secretstream_xchacha20poly1305_HEADERBYTES,
+    )
+
+    rc = lib.crypto_secretstream_xchacha20poly1305_init_push(
+        state.statebuf, headerbuf, key
+    )
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+
+    return ffi.buffer(headerbuf)[:]
+
+
+def crypto_secretstream_xchacha20poly1305_push(
+    state: crypto_secretstream_xchacha20poly1305_state,
+    m: bytes,
+    ad: Optional[bytes] = None,
+    tag: int = crypto_secretstream_xchacha20poly1305_TAG_MESSAGE,
+) -> bytes:
+    """
+    Add an encrypted message to the secret stream.
+
+    :param state: a secretstream state object
+    :type state: crypto_secretstream_xchacha20poly1305_state
+    :param m: the message to encrypt, the maximum length of an individual
+              message is
+              :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX`.
+    :type m: bytes
+    :param ad: additional data to include in the authentication tag
+    :type ad: bytes or None
+    :param tag: the message tag, usually
+                :data:`.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE` or
+                :data:`.crypto_secretstream_xchacha20poly1305_TAG_FINAL`.
+    :type tag: int
+    :return: ciphertext
+    :rtype: bytes
+
+    """
+    ensure(
+        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
+        "State must be a crypto_secretstream_xchacha20poly1305_state object",
+        raising=exc.TypeError,
+    )
+    ensure(isinstance(m, bytes), "Message is not bytes", raising=exc.TypeError)
+    ensure(
+        len(m) <= crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX,
+        "Message is too long",
+        raising=exc.ValueError,
+    )
+    ensure(
+        ad is None or isinstance(ad, bytes),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    clen = len(m) + crypto_secretstream_xchacha20poly1305_ABYTES
+    if state.rawbuf is None or len(state.rawbuf) < clen:
+        state.rawbuf = ffi.new("unsigned char[]", clen)
+
+    if ad is None:
+        ad = ffi.NULL
+        adlen = 0
+    else:
+        adlen = len(ad)
+
+    rc = lib.crypto_secretstream_xchacha20poly1305_push(
+        state.statebuf,
+        state.rawbuf,
+        ffi.NULL,
+        m,
+        len(m),
+        ad,
+        adlen,
+        tag,
+    )
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+
+    return ffi.buffer(state.rawbuf, clen)[:]
+
+
+def crypto_secretstream_xchacha20poly1305_init_pull(
+    state: crypto_secretstream_xchacha20poly1305_state,
+    header: bytes,
+    key: bytes,
+) -> None:
+    """
+    Initialize a crypto_secretstream_xchacha20poly1305 decryption buffer.
+
+    :param state: a secretstream state object
+    :type state: crypto_secretstream_xchacha20poly1305_state
+    :param header: must be
+                :data:`.crypto_secretstream_xchacha20poly1305_HEADERBYTES` long
+    :type header: bytes
+    :param key: must be
+                :data:`.crypto_secretstream_xchacha20poly1305_KEYBYTES` long
+    :type key: bytes
+
+    """
+    ensure(
+        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
+        "State must be a crypto_secretstream_xchacha20poly1305_state object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(header, bytes),
+        "Header must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+    ensure(
+        len(header) == crypto_secretstream_xchacha20poly1305_HEADERBYTES,
+        "Invalid header length",
+        raising=exc.ValueError,
+    )
+    ensure(
+        isinstance(key, bytes),
+        "Key must be a bytes sequence",
+        raising=exc.TypeError,
+    )
+    ensure(
+        len(key) == crypto_secretstream_xchacha20poly1305_KEYBYTES,
+        "Invalid key length",
+        raising=exc.ValueError,
+    )
+
+    if state.tagbuf is None:
+        state.tagbuf = ffi.new("unsigned char *")
+
+    rc = lib.crypto_secretstream_xchacha20poly1305_init_pull(
+        state.statebuf, header, key
+    )
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+
+
+def crypto_secretstream_xchacha20poly1305_pull(
+    state: crypto_secretstream_xchacha20poly1305_state,
+    c: bytes,
+    ad: Optional[bytes] = None,
+) -> Tuple[bytes, int]:
+    """
+    Read a decrypted message from the secret stream.
+
+    :param state: a secretstream state object
+    :type state: crypto_secretstream_xchacha20poly1305_state
+    :param c: the ciphertext to decrypt, the maximum length of an individual
+              ciphertext is
+              :data:`.crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX` +
+              :data:`.crypto_secretstream_xchacha20poly1305_ABYTES`.
+    :type c: bytes
+    :param ad: additional data to include in the authentication tag
+    :type ad: bytes or None
+    :return: (message, tag)
+    :rtype: (bytes, int)
+
+    """
+    ensure(
+        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
+        "State must be a crypto_secretstream_xchacha20poly1305_state object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        state.tagbuf is not None,
+        (
+            "State must be initialized using "
+            "crypto_secretstream_xchacha20poly1305_init_pull"
+        ),
+        raising=exc.ValueError,
+    )
+    ensure(
+        isinstance(c, bytes),
+        "Ciphertext is not bytes",
+        raising=exc.TypeError,
+    )
+    ensure(
+        len(c) >= crypto_secretstream_xchacha20poly1305_ABYTES,
+        "Ciphertext is too short",
+        raising=exc.ValueError,
+    )
+    ensure(
+        len(c)
+        <= (
+            crypto_secretstream_xchacha20poly1305_MESSAGEBYTES_MAX
+            + crypto_secretstream_xchacha20poly1305_ABYTES
+        ),
+        "Ciphertext is too long",
+        raising=exc.ValueError,
+    )
+    ensure(
+        ad is None or isinstance(ad, bytes),
+        "Additional data must be bytes or None",
+        raising=exc.TypeError,
+    )
+
+    mlen = len(c) - crypto_secretstream_xchacha20poly1305_ABYTES
+    if state.rawbuf is None or len(state.rawbuf) < mlen:
+        state.rawbuf = ffi.new("unsigned char[]", mlen)
+
+    if ad is None:
+        ad = ffi.NULL
+        adlen = 0
+    else:
+        adlen = len(ad)
+
+    rc = lib.crypto_secretstream_xchacha20poly1305_pull(
+        state.statebuf,
+        state.rawbuf,
+        ffi.NULL,
+        state.tagbuf,
+        c,
+        len(c),
+        ad,
+        adlen,
+    )
+    ensure(rc == 0, "Unexpected failure", raising=exc.RuntimeError)
+
+    # Cast safety: we `ensure` above that `state.tagbuf is not None`.
+    return (
+        ffi.buffer(state.rawbuf, mlen)[:],
+        int(cast(bytes, state.tagbuf)[0]),
+    )
+
+
+def crypto_secretstream_xchacha20poly1305_rekey(
+    state: crypto_secretstream_xchacha20poly1305_state,
+) -> None:
+    """
+    Explicitly change the encryption key in the stream.
+
+    Normally the stream is re-keyed as needed or an explicit ``tag`` of
+    :data:`.crypto_secretstream_xchacha20poly1305_TAG_REKEY` is added to a
+    message to ensure forward secrecy, but this method can be used instead
+    if the re-keying is controlled without adding the tag.
+
+    :param state: a secretstream state object
+    :type state: crypto_secretstream_xchacha20poly1305_state
+
+    """
+    ensure(
+        isinstance(state, crypto_secretstream_xchacha20poly1305_state),
+        "State must be a crypto_secretstream_xchacha20poly1305_state object",
+        raising=exc.TypeError,
+    )
+    lib.crypto_secretstream_xchacha20poly1305_rekey(state.statebuf)
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_shorthash.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_shorthash.py
new file mode 100644
index 00000000..8f7d209e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_shorthash.py
@@ -0,0 +1,81 @@
+# Copyright 2016 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import nacl.exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+has_crypto_shorthash_siphashx24 = bool(
+    lib.PYNACL_HAS_CRYPTO_SHORTHASH_SIPHASHX24
+)
+
+BYTES: int = lib.crypto_shorthash_siphash24_bytes()
+KEYBYTES: int = lib.crypto_shorthash_siphash24_keybytes()
+
+XBYTES = 0
+XKEYBYTES = 0
+
+if has_crypto_shorthash_siphashx24:
+    XBYTES = lib.crypto_shorthash_siphashx24_bytes()
+    XKEYBYTES = lib.crypto_shorthash_siphashx24_keybytes()
+
+
+def crypto_shorthash_siphash24(data: bytes, key: bytes) -> bytes:
+    """Compute a fast, cryptographic quality, keyed hash of the input data
+
+    :param data:
+    :type data: bytes
+    :param key: len(key) must be equal to
+                :py:data:`.KEYBYTES` (16)
+    :type key: bytes
+    """
+    if len(key) != KEYBYTES:
+        raise exc.ValueError(
+            "Key length must be exactly {} bytes".format(KEYBYTES)
+        )
+    digest = ffi.new("unsigned char[]", BYTES)
+    rc = lib.crypto_shorthash_siphash24(digest, data, len(data), key)
+
+    ensure(rc == 0, raising=exc.RuntimeError)
+    return ffi.buffer(digest, BYTES)[:]
+
+
+def crypto_shorthash_siphashx24(data: bytes, key: bytes) -> bytes:
+    """Compute a fast, cryptographic quality, keyed hash of the input data
+
+    :param data:
+    :type data: bytes
+    :param key: len(key) must be equal to
+                :py:data:`.XKEYBYTES` (16)
+    :type key: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+    """
+    ensure(
+        has_crypto_shorthash_siphashx24,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    if len(key) != XKEYBYTES:
+        raise exc.ValueError(
+            "Key length must be exactly {} bytes".format(XKEYBYTES)
+        )
+    digest = ffi.new("unsigned char[]", XBYTES)
+    rc = lib.crypto_shorthash_siphashx24(digest, data, len(data), key)
+
+    ensure(rc == 0, raising=exc.RuntimeError)
+    return ffi.buffer(digest, XBYTES)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_sign.py b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_sign.py
new file mode 100644
index 00000000..de3be47a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/crypto_sign.py
@@ -0,0 +1,327 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import Tuple
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+crypto_sign_BYTES: int = lib.crypto_sign_bytes()
+# crypto_sign_SEEDBYTES = lib.crypto_sign_seedbytes()
+crypto_sign_SEEDBYTES: int = lib.crypto_sign_secretkeybytes() // 2
+crypto_sign_PUBLICKEYBYTES: int = lib.crypto_sign_publickeybytes()
+crypto_sign_SECRETKEYBYTES: int = lib.crypto_sign_secretkeybytes()
+
+crypto_sign_curve25519_BYTES: int = lib.crypto_box_secretkeybytes()
+
+crypto_sign_ed25519ph_STATEBYTES: int = lib.crypto_sign_ed25519ph_statebytes()
+
+
+def crypto_sign_keypair() -> Tuple[bytes, bytes]:
+    """
+    Returns a randomly generated public key and secret key.
+
+    :rtype: (bytes(public_key), bytes(secret_key))
+    """
+    pk = ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES)
+    sk = ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES)
+
+    rc = lib.crypto_sign_keypair(pk, sk)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return (
+        ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:],
+        ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:],
+    )
+
+
+def crypto_sign_seed_keypair(seed: bytes) -> Tuple[bytes, bytes]:
+    """
+    Computes and returns the public key and secret key using the seed ``seed``.
+
+    :param seed: bytes
+    :rtype: (bytes(public_key), bytes(secret_key))
+    """
+    if len(seed) != crypto_sign_SEEDBYTES:
+        raise exc.ValueError("Invalid seed")
+
+    pk = ffi.new("unsigned char[]", crypto_sign_PUBLICKEYBYTES)
+    sk = ffi.new("unsigned char[]", crypto_sign_SECRETKEYBYTES)
+
+    rc = lib.crypto_sign_seed_keypair(pk, sk, seed)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return (
+        ffi.buffer(pk, crypto_sign_PUBLICKEYBYTES)[:],
+        ffi.buffer(sk, crypto_sign_SECRETKEYBYTES)[:],
+    )
+
+
+def crypto_sign(message: bytes, sk: bytes) -> bytes:
+    """
+    Signs the message ``message`` using the secret key ``sk`` and returns the
+    signed message.
+
+    :param message: bytes
+    :param sk: bytes
+    :rtype: bytes
+    """
+    signed = ffi.new("unsigned char[]", len(message) + crypto_sign_BYTES)
+    signed_len = ffi.new("unsigned long long *")
+
+    rc = lib.crypto_sign(signed, signed_len, message, len(message), sk)
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(signed, signed_len[0])[:]
+
+
+def crypto_sign_open(signed: bytes, pk: bytes) -> bytes:
+    """
+    Verifies the signature of the signed message ``signed`` using the public
+    key ``pk`` and returns the unsigned message.
+
+    :param signed: bytes
+    :param pk: bytes
+    :rtype: bytes
+    """
+    message = ffi.new("unsigned char[]", len(signed))
+    message_len = ffi.new("unsigned long long *")
+
+    if (
+        lib.crypto_sign_open(message, message_len, signed, len(signed), pk)
+        != 0
+    ):
+        raise exc.BadSignatureError("Signature was forged or corrupt")
+
+    return ffi.buffer(message, message_len[0])[:]
+
+
+def crypto_sign_ed25519_pk_to_curve25519(public_key_bytes: bytes) -> bytes:
+    """
+    Converts a public Ed25519 key (encoded as bytes ``public_key_bytes``) to
+    a public Curve25519 key as bytes.
+
+    Raises a ValueError if ``public_key_bytes`` is not of length
+    ``crypto_sign_PUBLICKEYBYTES``
+
+    :param public_key_bytes: bytes
+    :rtype: bytes
+    """
+    if len(public_key_bytes) != crypto_sign_PUBLICKEYBYTES:
+        raise exc.ValueError("Invalid curve public key")
+
+    curve_public_key_len = crypto_sign_curve25519_BYTES
+    curve_public_key = ffi.new("unsigned char[]", curve_public_key_len)
+
+    rc = lib.crypto_sign_ed25519_pk_to_curve25519(
+        curve_public_key, public_key_bytes
+    )
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(curve_public_key, curve_public_key_len)[:]
+
+
+def crypto_sign_ed25519_sk_to_curve25519(secret_key_bytes: bytes) -> bytes:
+    """
+    Converts a secret Ed25519 key (encoded as bytes ``secret_key_bytes``) to
+    a secret Curve25519 key as bytes.
+
+    Raises a ValueError if ``secret_key_bytes``is not of length
+    ``crypto_sign_SECRETKEYBYTES``
+
+    :param secret_key_bytes: bytes
+    :rtype: bytes
+    """
+    if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES:
+        raise exc.ValueError("Invalid curve secret key")
+
+    curve_secret_key_len = crypto_sign_curve25519_BYTES
+    curve_secret_key = ffi.new("unsigned char[]", curve_secret_key_len)
+
+    rc = lib.crypto_sign_ed25519_sk_to_curve25519(
+        curve_secret_key, secret_key_bytes
+    )
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(curve_secret_key, curve_secret_key_len)[:]
+
+
+def crypto_sign_ed25519_sk_to_pk(secret_key_bytes: bytes) -> bytes:
+    """
+    Extract the public Ed25519 key from a secret Ed25519 key (encoded
+    as bytes ``secret_key_bytes``).
+
+    Raises a ValueError if ``secret_key_bytes``is not of length
+    ``crypto_sign_SECRETKEYBYTES``
+
+    :param secret_key_bytes: bytes
+    :rtype: bytes
+    """
+    if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES:
+        raise exc.ValueError("Invalid secret key")
+
+    return secret_key_bytes[crypto_sign_SEEDBYTES:]
+
+
+def crypto_sign_ed25519_sk_to_seed(secret_key_bytes: bytes) -> bytes:
+    """
+    Extract the seed from a secret Ed25519 key (encoded
+    as bytes ``secret_key_bytes``).
+
+    Raises a ValueError if ``secret_key_bytes``is not of length
+    ``crypto_sign_SECRETKEYBYTES``
+
+    :param secret_key_bytes: bytes
+    :rtype: bytes
+    """
+    if len(secret_key_bytes) != crypto_sign_SECRETKEYBYTES:
+        raise exc.ValueError("Invalid secret key")
+
+    return secret_key_bytes[:crypto_sign_SEEDBYTES]
+
+
+class crypto_sign_ed25519ph_state:
+    """
+    State object wrapping the sha-512 state used in ed25519ph computation
+    """
+
+    __slots__ = ["state"]
+
+    def __init__(self) -> None:
+        self.state: bytes = ffi.new(
+            "unsigned char[]", crypto_sign_ed25519ph_STATEBYTES
+        )
+
+        rc = lib.crypto_sign_ed25519ph_init(self.state)
+
+        ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+
+def crypto_sign_ed25519ph_update(
+    edph: crypto_sign_ed25519ph_state, pmsg: bytes
+) -> None:
+    """
+    Update the hash state wrapped in edph
+
+    :param edph: the ed25519ph state being updated
+    :type edph: crypto_sign_ed25519ph_state
+    :param pmsg: the partial message
+    :type pmsg: bytes
+    :rtype: None
+    """
+    ensure(
+        isinstance(edph, crypto_sign_ed25519ph_state),
+        "edph parameter must be a ed25519ph_state object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(pmsg, bytes),
+        "pmsg parameter must be a bytes object",
+        raising=exc.TypeError,
+    )
+    rc = lib.crypto_sign_ed25519ph_update(edph.state, pmsg, len(pmsg))
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+
+def crypto_sign_ed25519ph_final_create(
+    edph: crypto_sign_ed25519ph_state, sk: bytes
+) -> bytes:
+    """
+    Create a signature for the data hashed in edph
+    using the secret key sk
+
+    :param edph: the ed25519ph state for the data
+                 being signed
+    :type edph: crypto_sign_ed25519ph_state
+    :param sk: the ed25519 secret part of the signing key
+    :type sk: bytes
+    :return: ed25519ph signature
+    :rtype: bytes
+    """
+    ensure(
+        isinstance(edph, crypto_sign_ed25519ph_state),
+        "edph parameter must be a ed25519ph_state object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(sk, bytes),
+        "secret key parameter must be a bytes object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        len(sk) == crypto_sign_SECRETKEYBYTES,
+        ("secret key must be {} bytes long").format(
+            crypto_sign_SECRETKEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+    signature = ffi.new("unsigned char[]", crypto_sign_BYTES)
+    rc = lib.crypto_sign_ed25519ph_final_create(
+        edph.state, signature, ffi.NULL, sk
+    )
+    ensure(rc == 0, "Unexpected library error", raising=exc.RuntimeError)
+
+    return ffi.buffer(signature, crypto_sign_BYTES)[:]
+
+
+def crypto_sign_ed25519ph_final_verify(
+    edph: crypto_sign_ed25519ph_state, signature: bytes, pk: bytes
+) -> bool:
+    """
+    Verify a prehashed signature using the public key pk
+
+    :param edph: the ed25519ph state for the data
+                 being verified
+    :type edph: crypto_sign_ed25519ph_state
+    :param signature: the signature being verified
+    :type signature: bytes
+    :param pk: the ed25519 public part of the signing key
+    :type pk: bytes
+    :return: True if the signature is valid
+    :rtype: boolean
+    :raises exc.BadSignatureError: if the signature is not valid
+    """
+    ensure(
+        isinstance(edph, crypto_sign_ed25519ph_state),
+        "edph parameter must be a ed25519ph_state object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(signature, bytes),
+        "signature parameter must be a bytes object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        len(signature) == crypto_sign_BYTES,
+        ("signature must be {} bytes long").format(crypto_sign_BYTES),
+        raising=exc.TypeError,
+    )
+    ensure(
+        isinstance(pk, bytes),
+        "public key parameter must be a bytes object",
+        raising=exc.TypeError,
+    )
+    ensure(
+        len(pk) == crypto_sign_PUBLICKEYBYTES,
+        ("public key must be {} bytes long").format(
+            crypto_sign_PUBLICKEYBYTES
+        ),
+        raising=exc.TypeError,
+    )
+    rc = lib.crypto_sign_ed25519ph_final_verify(edph.state, signature, pk)
+    if rc != 0:
+        raise exc.BadSignatureError("Signature was forged or corrupt")
+
+    return True
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/randombytes.py b/.venv/lib/python3.12/site-packages/nacl/bindings/randombytes.py
new file mode 100644
index 00000000..ed76deb5
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/randombytes.py
@@ -0,0 +1,51 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+
+randombytes_SEEDBYTES: int = lib.randombytes_seedbytes()
+
+
+def randombytes(size: int) -> bytes:
+    """
+    Returns ``size`` number of random bytes from a cryptographically secure
+    random source.
+
+    :param size: int
+    :rtype: bytes
+    """
+    buf = ffi.new("unsigned char[]", size)
+    lib.randombytes(buf, size)
+    return ffi.buffer(buf, size)[:]
+
+
+def randombytes_buf_deterministic(size: int, seed: bytes) -> bytes:
+    """
+    Returns ``size`` number of deterministically generated pseudorandom bytes
+    from a seed
+
+    :param size: int
+    :param seed: bytes
+    :rtype: bytes
+    """
+    if len(seed) != randombytes_SEEDBYTES:
+        raise exc.TypeError(
+            "Deterministic random bytes must be generated from 32 bytes"
+        )
+
+    buf = ffi.new("unsigned char[]", size)
+    lib.randombytes_buf_deterministic(buf, size, seed)
+    return ffi.buffer(buf, size)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/sodium_core.py b/.venv/lib/python3.12/site-packages/nacl/bindings/sodium_core.py
new file mode 100644
index 00000000..7ebb84c8
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/sodium_core.py
@@ -0,0 +1,33 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from nacl import exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+def _sodium_init() -> None:
+    ensure(
+        lib.sodium_init() != -1,
+        "Could not initialize sodium",
+        raising=exc.RuntimeError,
+    )
+
+
+def sodium_init() -> None:
+    """
+    Initializes sodium, picking the best implementations available for this
+    machine.
+    """
+    ffi.init_once(_sodium_init, "libsodium")
diff --git a/.venv/lib/python3.12/site-packages/nacl/bindings/utils.py b/.venv/lib/python3.12/site-packages/nacl/bindings/utils.py
new file mode 100644
index 00000000..0ff22e34
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/bindings/utils.py
@@ -0,0 +1,141 @@
+# Copyright 2013-2017 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import nacl.exceptions as exc
+from nacl._sodium import ffi, lib
+from nacl.exceptions import ensure
+
+
+def sodium_memcmp(inp1: bytes, inp2: bytes) -> bool:
+    """
+    Compare contents of two memory regions in constant time
+    """
+    ensure(isinstance(inp1, bytes), raising=exc.TypeError)
+    ensure(isinstance(inp2, bytes), raising=exc.TypeError)
+
+    ln = max(len(inp1), len(inp2))
+
+    buf1 = ffi.new("char []", ln)
+    buf2 = ffi.new("char []", ln)
+
+    ffi.memmove(buf1, inp1, len(inp1))
+    ffi.memmove(buf2, inp2, len(inp2))
+
+    eqL = len(inp1) == len(inp2)
+    eqC = lib.sodium_memcmp(buf1, buf2, ln) == 0
+
+    return eqL and eqC
+
+
+def sodium_pad(s: bytes, blocksize: int) -> bytes:
+    """
+    Pad the input bytearray ``s`` to a multiple of ``blocksize``
+    using the ISO/IEC 7816-4 algorithm
+
+    :param s: input bytes string
+    :type s: bytes
+    :param blocksize:
+    :type blocksize: int
+    :return: padded string
+    :rtype: bytes
+    """
+    ensure(isinstance(s, bytes), raising=exc.TypeError)
+    ensure(isinstance(blocksize, int), raising=exc.TypeError)
+    if blocksize <= 0:
+        raise exc.ValueError
+    s_len = len(s)
+    m_len = s_len + blocksize
+    buf = ffi.new("unsigned char []", m_len)
+    p_len = ffi.new("size_t []", 1)
+    ffi.memmove(buf, s, s_len)
+    rc = lib.sodium_pad(p_len, buf, s_len, blocksize, m_len)
+    ensure(rc == 0, "Padding failure", raising=exc.CryptoError)
+    return ffi.buffer(buf, p_len[0])[:]
+
+
+def sodium_unpad(s: bytes, blocksize: int) -> bytes:
+    """
+    Remove ISO/IEC 7816-4 padding from the input byte array ``s``
+
+    :param s: input bytes string
+    :type s: bytes
+    :param blocksize:
+    :type blocksize: int
+    :return: unpadded string
+    :rtype: bytes
+    """
+    ensure(isinstance(s, bytes), raising=exc.TypeError)
+    ensure(isinstance(blocksize, int), raising=exc.TypeError)
+    s_len = len(s)
+    u_len = ffi.new("size_t []", 1)
+    rc = lib.sodium_unpad(u_len, s, s_len, blocksize)
+    if rc != 0:
+        raise exc.CryptoError("Unpadding failure")
+    return s[: u_len[0]]
+
+
+def sodium_increment(inp: bytes) -> bytes:
+    """
+    Increment the value of a byte-sequence interpreted
+    as the little-endian representation of a unsigned big integer.
+
+    :param inp: input bytes buffer
+    :type inp: bytes
+    :return: a byte-sequence representing, as a little-endian
+             unsigned big integer, the value ``to_int(inp)``
+             incremented by one.
+    :rtype: bytes
+
+    """
+    ensure(isinstance(inp, bytes), raising=exc.TypeError)
+
+    ln = len(inp)
+    buf = ffi.new("unsigned char []", ln)
+
+    ffi.memmove(buf, inp, ln)
+
+    lib.sodium_increment(buf, ln)
+
+    return ffi.buffer(buf, ln)[:]
+
+
+def sodium_add(a: bytes, b: bytes) -> bytes:
+    """
+    Given a couple of *same-sized* byte sequences, interpreted as the
+    little-endian representation of two unsigned integers, compute
+    the modular addition of the represented values, in constant time for
+    a given common length of the byte sequences.
+
+    :param a: input bytes buffer
+    :type a: bytes
+    :param b: input bytes buffer
+    :type b: bytes
+    :return: a byte-sequence representing, as a little-endian big integer,
+             the integer value of ``(to_int(a) + to_int(b)) mod 2^(8*len(a))``
+    :rtype: bytes
+    """
+    ensure(isinstance(a, bytes), raising=exc.TypeError)
+    ensure(isinstance(b, bytes), raising=exc.TypeError)
+    ln = len(a)
+    ensure(len(b) == ln, raising=exc.TypeError)
+
+    buf_a = ffi.new("unsigned char []", ln)
+    buf_b = ffi.new("unsigned char []", ln)
+
+    ffi.memmove(buf_a, a, ln)
+    ffi.memmove(buf_b, b, ln)
+
+    lib.sodium_add(buf_a, buf_b, ln)
+
+    return ffi.buffer(buf_a, ln)[:]
diff --git a/.venv/lib/python3.12/site-packages/nacl/encoding.py b/.venv/lib/python3.12/site-packages/nacl/encoding.py
new file mode 100644
index 00000000..6740cfb3
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/encoding.py
@@ -0,0 +1,105 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+import base64
+import binascii
+from abc import ABCMeta, abstractmethod
+from typing import SupportsBytes, Type
+
+
+# TODO: when the minimum supported version of Python is 3.8, we can import
+# Protocol from typing, and replace Encoder with a Protocol instead.
+class _Encoder(metaclass=ABCMeta):
+    @staticmethod
+    @abstractmethod
+    def encode(data: bytes) -> bytes:
+        """Transform raw data to encoded data."""
+
+    @staticmethod
+    @abstractmethod
+    def decode(data: bytes) -> bytes:
+        """Transform encoded data back to raw data.
+
+        Decoding after encoding should be a no-op, i.e. `decode(encode(x)) == x`.
+        """
+
+
+# Functions that use encoders are passed a subclass of _Encoder, not an instance
+# (because the methods are all static). Let's gloss over that detail by defining
+# an alias for Type[_Encoder].
+Encoder = Type[_Encoder]
+
+
+class RawEncoder(_Encoder):
+    @staticmethod
+    def encode(data: bytes) -> bytes:
+        return data
+
+    @staticmethod
+    def decode(data: bytes) -> bytes:
+        return data
+
+
+class HexEncoder(_Encoder):
+    @staticmethod
+    def encode(data: bytes) -> bytes:
+        return binascii.hexlify(data)
+
+    @staticmethod
+    def decode(data: bytes) -> bytes:
+        return binascii.unhexlify(data)
+
+
+class Base16Encoder(_Encoder):
+    @staticmethod
+    def encode(data: bytes) -> bytes:
+        return base64.b16encode(data)
+
+    @staticmethod
+    def decode(data: bytes) -> bytes:
+        return base64.b16decode(data)
+
+
+class Base32Encoder(_Encoder):
+    @staticmethod
+    def encode(data: bytes) -> bytes:
+        return base64.b32encode(data)
+
+    @staticmethod
+    def decode(data: bytes) -> bytes:
+        return base64.b32decode(data)
+
+
+class Base64Encoder(_Encoder):
+    @staticmethod
+    def encode(data: bytes) -> bytes:
+        return base64.b64encode(data)
+
+    @staticmethod
+    def decode(data: bytes) -> bytes:
+        return base64.b64decode(data)
+
+
+class URLSafeBase64Encoder(_Encoder):
+    @staticmethod
+    def encode(data: bytes) -> bytes:
+        return base64.urlsafe_b64encode(data)
+
+    @staticmethod
+    def decode(data: bytes) -> bytes:
+        return base64.urlsafe_b64decode(data)
+
+
+class Encodable:
+    def encode(self: SupportsBytes, encoder: Encoder = RawEncoder) -> bytes:
+        return encoder.encode(bytes(self))
diff --git a/.venv/lib/python3.12/site-packages/nacl/exceptions.py b/.venv/lib/python3.12/site-packages/nacl/exceptions.py
new file mode 100644
index 00000000..e321df7b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/exceptions.py
@@ -0,0 +1,88 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+# We create a clone of various builtin Exception types which additionally
+# inherit from CryptoError. Below, we refer to the parent types via the
+# `builtins` namespace, so mypy can distinguish between (e.g.)
+# `nacl.exceptions.RuntimeError` and `builtins.RuntimeError`.
+import builtins
+from typing import Type
+
+
+class CryptoError(Exception):
+    """
+    Base exception for all nacl related errors
+    """
+
+
+class BadSignatureError(CryptoError):
+    """
+    Raised when the signature was forged or otherwise corrupt.
+    """
+
+
+class RuntimeError(builtins.RuntimeError, CryptoError):
+    pass
+
+
+class AssertionError(builtins.AssertionError, CryptoError):
+    pass
+
+
+class TypeError(builtins.TypeError, CryptoError):
+    pass
+
+
+class ValueError(builtins.ValueError, CryptoError):
+    pass
+
+
+class InvalidkeyError(CryptoError):
+    pass
+
+
+class CryptPrefixError(InvalidkeyError):
+    pass
+
+
+class UnavailableError(RuntimeError):
+    """
+    is a subclass of :class:`~nacl.exceptions.RuntimeError`, raised when
+    trying to call functions not available in a minimal build of
+    libsodium.
+    """
+
+    pass
+
+
+def ensure(cond: bool, *args: object, **kwds: Type[Exception]) -> None:
+    """
+    Return if a condition is true, otherwise raise a caller-configurable
+    :py:class:`Exception`
+    :param bool cond: the condition to be checked
+    :param sequence args: the arguments to be passed to the exception's
+                          constructor
+    The only accepted named parameter is `raising` used to configure the
+    exception to be raised if `cond` is not `True`
+    """
+    _CHK_UNEXP = "check_condition() got an unexpected keyword argument {0}"
+
+    raising = kwds.pop("raising", AssertionError)
+    if kwds:
+        raise TypeError(_CHK_UNEXP.format(repr(kwds.popitem()[0])))
+
+    if cond is True:
+        return
+    raise raising(*args)
diff --git a/.venv/lib/python3.12/site-packages/nacl/hash.py b/.venv/lib/python3.12/site-packages/nacl/hash.py
new file mode 100644
index 00000000..eb8cff6b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/hash.py
@@ -0,0 +1,182 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+"""
+The :mod:`nacl.hash` module exposes one-shot interfaces
+for libsodium selected hash primitives and the constants needed
+for their usage.
+"""
+
+
+import nacl.bindings
+import nacl.encoding
+
+
+BLAKE2B_BYTES = nacl.bindings.crypto_generichash_BYTES
+"""Default digest size for :func:`blake2b` hash"""
+BLAKE2B_BYTES_MIN = nacl.bindings.crypto_generichash_BYTES_MIN
+"""Minimum allowed digest size for :func:`blake2b` hash"""
+BLAKE2B_BYTES_MAX = nacl.bindings.crypto_generichash_BYTES_MAX
+"""Maximum allowed digest size for :func:`blake2b` hash"""
+BLAKE2B_KEYBYTES = nacl.bindings.crypto_generichash_KEYBYTES
+"""Default size of the ``key`` byte array for :func:`blake2b` hash"""
+BLAKE2B_KEYBYTES_MIN = nacl.bindings.crypto_generichash_KEYBYTES_MIN
+"""Minimum allowed size of the ``key`` byte array for :func:`blake2b` hash"""
+BLAKE2B_KEYBYTES_MAX = nacl.bindings.crypto_generichash_KEYBYTES_MAX
+"""Maximum allowed size of the ``key`` byte array for :func:`blake2b` hash"""
+BLAKE2B_SALTBYTES = nacl.bindings.crypto_generichash_SALTBYTES
+"""Maximum allowed length of the ``salt`` byte array for
+:func:`blake2b` hash"""
+BLAKE2B_PERSONALBYTES = nacl.bindings.crypto_generichash_PERSONALBYTES
+"""Maximum allowed length of the ``personalization``
+byte array for :func:`blake2b` hash"""
+
+SIPHASH_BYTES = nacl.bindings.crypto_shorthash_siphash24_BYTES
+"""Size of the :func:`siphash24` digest"""
+SIPHASH_KEYBYTES = nacl.bindings.crypto_shorthash_siphash24_KEYBYTES
+"""Size of the secret ``key`` used by the :func:`siphash24` MAC"""
+
+SIPHASHX_AVAILABLE = nacl.bindings.has_crypto_shorthash_siphashx24
+"""``True`` if :func:`siphashx24` is available to be called"""
+
+SIPHASHX_BYTES = nacl.bindings.crypto_shorthash_siphashx24_BYTES
+"""Size of the :func:`siphashx24` digest"""
+SIPHASHX_KEYBYTES = nacl.bindings.crypto_shorthash_siphashx24_KEYBYTES
+"""Size of the secret ``key`` used by the :func:`siphashx24` MAC"""
+
+_b2b_hash = nacl.bindings.crypto_generichash_blake2b_salt_personal
+_sip_hash = nacl.bindings.crypto_shorthash_siphash24
+_sip_hashx = nacl.bindings.crypto_shorthash_siphashx24
+
+
+def sha256(
+    message: bytes, encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder
+) -> bytes:
+    """
+    Hashes ``message`` with SHA256.
+
+    :param message: The message to hash.
+    :type message: bytes
+    :param encoder: A class that is able to encode the hashed message.
+    :returns: The hashed message.
+    :rtype: bytes
+    """
+    return encoder.encode(nacl.bindings.crypto_hash_sha256(message))
+
+
+def sha512(
+    message: bytes, encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder
+) -> bytes:
+    """
+    Hashes ``message`` with SHA512.
+
+    :param message: The message to hash.
+    :type message: bytes
+    :param encoder: A class that is able to encode the hashed message.
+    :returns: The hashed message.
+    :rtype: bytes
+    """
+    return encoder.encode(nacl.bindings.crypto_hash_sha512(message))
+
+
+def blake2b(
+    data: bytes,
+    digest_size: int = BLAKE2B_BYTES,
+    key: bytes = b"",
+    salt: bytes = b"",
+    person: bytes = b"",
+    encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder,
+) -> bytes:
+    """
+    Hashes ``data`` with blake2b.
+
+    :param data: the digest input byte sequence
+    :type data: bytes
+    :param digest_size: the requested digest size; must be at most
+                        :const:`BLAKE2B_BYTES_MAX`;
+                        the default digest size is
+                        :const:`BLAKE2B_BYTES`
+    :type digest_size: int
+    :param key: the key to be set for keyed MAC/PRF usage; if set, the key
+                must be at most :data:`~nacl.hash.BLAKE2B_KEYBYTES_MAX` long
+    :type key: bytes
+    :param salt: an initialization salt at most
+                 :const:`BLAKE2B_SALTBYTES` long;
+                 it will be zero-padded if needed
+    :type salt: bytes
+    :param person: a personalization string at most
+                   :const:`BLAKE2B_PERSONALBYTES` long;
+                   it will be zero-padded if needed
+    :type person: bytes
+    :param encoder: the encoder to use on returned digest
+    :type encoder: class
+    :returns: The hashed message.
+    :rtype: bytes
+    """
+
+    digest = _b2b_hash(
+        data, digest_size=digest_size, key=key, salt=salt, person=person
+    )
+    return encoder.encode(digest)
+
+
+generichash = blake2b
+
+
+def siphash24(
+    message: bytes,
+    key: bytes = b"",
+    encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder,
+) -> bytes:
+    """
+    Computes a keyed MAC of ``message`` using the short-input-optimized
+    siphash-2-4 construction.
+
+    :param message: The message to hash.
+    :type message: bytes
+    :param key: the message authentication key for the siphash MAC construct
+    :type key: bytes(:const:`SIPHASH_KEYBYTES`)
+    :param encoder: A class that is able to encode the hashed message.
+    :returns: The hashed message.
+    :rtype: bytes(:const:`SIPHASH_BYTES`)
+    """
+    digest = _sip_hash(message, key)
+    return encoder.encode(digest)
+
+
+shorthash = siphash24
+
+
+def siphashx24(
+    message: bytes,
+    key: bytes = b"",
+    encoder: nacl.encoding.Encoder = nacl.encoding.HexEncoder,
+) -> bytes:
+    """
+    Computes a keyed MAC of ``message`` using the 128 bit variant of the
+    siphash-2-4 construction.
+
+    :param message: The message to hash.
+    :type message: bytes
+    :param key: the message authentication key for the siphash MAC construct
+    :type key: bytes(:const:`SIPHASHX_KEYBYTES`)
+    :param encoder: A class that is able to encode the hashed message.
+    :returns: The hashed message.
+    :rtype: bytes(:const:`SIPHASHX_BYTES`)
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+
+    .. versionadded:: 1.2
+    """
+    digest = _sip_hashx(message, key)
+    return encoder.encode(digest)
diff --git a/.venv/lib/python3.12/site-packages/nacl/hashlib.py b/.venv/lib/python3.12/site-packages/nacl/hashlib.py
new file mode 100644
index 00000000..4fd4a976
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/hashlib.py
@@ -0,0 +1,143 @@
+# Copyright 2016-2019 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import binascii
+from typing import NoReturn
+
+import nacl.bindings
+from nacl.utils import bytes_as_string
+
+BYTES = nacl.bindings.crypto_generichash_BYTES
+BYTES_MIN = nacl.bindings.crypto_generichash_BYTES_MIN
+BYTES_MAX = nacl.bindings.crypto_generichash_BYTES_MAX
+KEYBYTES = nacl.bindings.crypto_generichash_KEYBYTES
+KEYBYTES_MIN = nacl.bindings.crypto_generichash_KEYBYTES_MIN
+KEYBYTES_MAX = nacl.bindings.crypto_generichash_KEYBYTES_MAX
+SALTBYTES = nacl.bindings.crypto_generichash_SALTBYTES
+PERSONALBYTES = nacl.bindings.crypto_generichash_PERSONALBYTES
+
+SCRYPT_AVAILABLE = nacl.bindings.has_crypto_pwhash_scryptsalsa208sha256
+
+_b2b_init = nacl.bindings.crypto_generichash_blake2b_init
+_b2b_final = nacl.bindings.crypto_generichash_blake2b_final
+_b2b_update = nacl.bindings.crypto_generichash_blake2b_update
+
+
+class blake2b:
+    """
+    :py:mod:`hashlib` API compatible blake2b algorithm implementation
+    """
+
+    MAX_DIGEST_SIZE = BYTES
+    MAX_KEY_SIZE = KEYBYTES_MAX
+    PERSON_SIZE = PERSONALBYTES
+    SALT_SIZE = SALTBYTES
+
+    def __init__(
+        self,
+        data: bytes = b"",
+        digest_size: int = BYTES,
+        key: bytes = b"",
+        salt: bytes = b"",
+        person: bytes = b"",
+    ):
+        """
+        :py:class:`.blake2b` algorithm initializer
+
+        :param data:
+        :type data: bytes
+        :param int digest_size: the requested digest size; must be
+                                at most :py:attr:`.MAX_DIGEST_SIZE`;
+                                the default digest size is :py:data:`.BYTES`
+        :param key: the key to be set for keyed MAC/PRF usage; if set,
+                    the key must be at most :py:data:`.KEYBYTES_MAX` long
+        :type key: bytes
+        :param salt: a initialization salt at most
+                     :py:attr:`.SALT_SIZE` long; it will be zero-padded
+                     if needed
+        :type salt: bytes
+        :param person: a personalization string at most
+                       :py:attr:`.PERSONAL_SIZE` long; it will be zero-padded
+                       if needed
+        :type person: bytes
+        """
+
+        self._state = _b2b_init(
+            key=key, salt=salt, person=person, digest_size=digest_size
+        )
+        self._digest_size = digest_size
+
+        if data:
+            self.update(data)
+
+    @property
+    def digest_size(self) -> int:
+        return self._digest_size
+
+    @property
+    def block_size(self) -> int:
+        return 128
+
+    @property
+    def name(self) -> str:
+        return "blake2b"
+
+    def update(self, data: bytes) -> None:
+        _b2b_update(self._state, data)
+
+    def digest(self) -> bytes:
+        _st = self._state.copy()
+        return _b2b_final(_st)
+
+    def hexdigest(self) -> str:
+        return bytes_as_string(binascii.hexlify(self.digest()))
+
+    def copy(self) -> "blake2b":
+        _cp = type(self)(digest_size=self.digest_size)
+        _st = self._state.copy()
+        _cp._state = _st
+        return _cp
+
+    def __reduce__(self) -> NoReturn:
+        """
+        Raise the same exception as hashlib's blake implementation
+        on copy.copy()
+        """
+        raise TypeError(
+            "can't pickle {} objects".format(self.__class__.__name__)
+        )
+
+
+def scrypt(
+    password: bytes,
+    salt: bytes = b"",
+    n: int = 2 ** 20,
+    r: int = 8,
+    p: int = 1,
+    maxmem: int = 2 ** 25,
+    dklen: int = 64,
+) -> bytes:
+    """
+    Derive a cryptographic key using the scrypt KDF.
+
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+
+    Implements the same signature as the ``hashlib.scrypt`` implemented
+    in cpython version 3.6
+    """
+    return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_ll(
+        password, salt, n, r, p, maxmem=maxmem, dklen=dklen
+    )
diff --git a/.venv/lib/python3.12/site-packages/nacl/public.py b/.venv/lib/python3.12/site-packages/nacl/public.py
new file mode 100644
index 00000000..be9410fc
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/public.py
@@ -0,0 +1,423 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import ClassVar, Generic, Optional, Type, TypeVar
+
+import nacl.bindings
+from nacl import encoding
+from nacl import exceptions as exc
+from nacl.encoding import Encoder
+from nacl.utils import EncryptedMessage, StringFixer, random
+
+
+class PublicKey(encoding.Encodable, StringFixer):
+    """
+    The public key counterpart to an Curve25519 :class:`nacl.public.PrivateKey`
+    for encrypting messages.
+
+    :param public_key: [:class:`bytes`] Encoded Curve25519 public key
+    :param encoder: A class that is able to decode the `public_key`
+
+    :cvar SIZE: The size that the public key is required to be
+    """
+
+    SIZE: ClassVar[int] = nacl.bindings.crypto_box_PUBLICKEYBYTES
+
+    def __init__(
+        self,
+        public_key: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ):
+        self._public_key = encoder.decode(public_key)
+        if not isinstance(self._public_key, bytes):
+            raise exc.TypeError("PublicKey must be created from 32 bytes")
+
+        if len(self._public_key) != self.SIZE:
+            raise exc.ValueError(
+                "The public key must be exactly {} bytes long".format(
+                    self.SIZE
+                )
+            )
+
+    def __bytes__(self) -> bytes:
+        return self._public_key
+
+    def __hash__(self) -> int:
+        return hash(bytes(self))
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, self.__class__):
+            return False
+        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
+
+    def __ne__(self, other: object) -> bool:
+        return not (self == other)
+
+
+class PrivateKey(encoding.Encodable, StringFixer):
+    """
+    Private key for decrypting messages using the Curve25519 algorithm.
+
+    .. warning:: This **must** be protected and remain secret. Anyone who
+        knows the value of your :class:`~nacl.public.PrivateKey` can decrypt
+        any message encrypted by the corresponding
+        :class:`~nacl.public.PublicKey`
+
+    :param private_key: The private key used to decrypt messages
+    :param encoder: The encoder class used to decode the given keys
+
+    :cvar SIZE: The size that the private key is required to be
+    :cvar SEED_SIZE: The size that the seed used to generate the
+                     private key is required to be
+    """
+
+    SIZE: ClassVar[int] = nacl.bindings.crypto_box_SECRETKEYBYTES
+    SEED_SIZE: ClassVar[int] = nacl.bindings.crypto_box_SEEDBYTES
+
+    def __init__(
+        self,
+        private_key: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ):
+        # Decode the secret_key
+        private_key = encoder.decode(private_key)
+        # verify the given secret key type and size are correct
+        if not (
+            isinstance(private_key, bytes) and len(private_key) == self.SIZE
+        ):
+            raise exc.TypeError(
+                (
+                    "PrivateKey must be created from a {} "
+                    "bytes long raw secret key"
+                ).format(self.SIZE)
+            )
+
+        raw_public_key = nacl.bindings.crypto_scalarmult_base(private_key)
+
+        self._private_key = private_key
+        self.public_key = PublicKey(raw_public_key)
+
+    @classmethod
+    def from_seed(
+        cls,
+        seed: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> "PrivateKey":
+        """
+        Generate a PrivateKey using a deterministic construction
+        starting from a caller-provided seed
+
+        .. warning:: The seed **must** be high-entropy; therefore,
+            its generator **must** be a cryptographic quality
+            random function like, for example, :func:`~nacl.utils.random`.
+
+        .. warning:: The seed **must** be protected and remain secret.
+            Anyone who knows the seed is really in possession of
+            the corresponding PrivateKey.
+
+        :param seed: The seed used to generate the private key
+        :rtype: :class:`~nacl.public.PrivateKey`
+        """
+        # decode the seed
+        seed = encoder.decode(seed)
+        # Verify the given seed type and size are correct
+        if not (isinstance(seed, bytes) and len(seed) == cls.SEED_SIZE):
+            raise exc.TypeError(
+                (
+                    "PrivateKey seed must be a {} bytes long "
+                    "binary sequence"
+                ).format(cls.SEED_SIZE)
+            )
+        # generate a raw keypair from the given seed
+        raw_pk, raw_sk = nacl.bindings.crypto_box_seed_keypair(seed)
+        # construct a instance from the raw secret key
+        return cls(raw_sk)
+
+    def __bytes__(self) -> bytes:
+        return self._private_key
+
+    def __hash__(self) -> int:
+        return hash((type(self), bytes(self.public_key)))
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, self.__class__):
+            return False
+        return self.public_key == other.public_key
+
+    def __ne__(self, other: object) -> bool:
+        return not (self == other)
+
+    @classmethod
+    def generate(cls) -> "PrivateKey":
+        """
+        Generates a random :class:`~nacl.public.PrivateKey` object
+
+        :rtype: :class:`~nacl.public.PrivateKey`
+        """
+        return cls(random(PrivateKey.SIZE), encoder=encoding.RawEncoder)
+
+
+_Box = TypeVar("_Box", bound="Box")
+
+
+class Box(encoding.Encodable, StringFixer):
+    """
+    The Box class boxes and unboxes messages between a pair of keys
+
+    The ciphertexts generated by :class:`~nacl.public.Box` include a 16
+    byte authenticator which is checked as part of the decryption. An invalid
+    authenticator will cause the decrypt function to raise an exception. The
+    authenticator is not a signature. Once you've decrypted the message you've
+    demonstrated the ability to create arbitrary valid message, so messages you
+    send are repudiable. For non-repudiable messages, sign them after
+    encryption.
+
+    :param private_key: :class:`~nacl.public.PrivateKey` used to encrypt and
+        decrypt messages
+    :param public_key: :class:`~nacl.public.PublicKey` used to encrypt and
+        decrypt messages
+
+    :cvar NONCE_SIZE: The size that the nonce is required to be.
+    """
+
+    NONCE_SIZE: ClassVar[int] = nacl.bindings.crypto_box_NONCEBYTES
+    _shared_key: bytes
+
+    def __init__(self, private_key: PrivateKey, public_key: PublicKey):
+        if not isinstance(private_key, PrivateKey) or not isinstance(
+            public_key, PublicKey
+        ):
+            raise exc.TypeError(
+                "Box must be created from a PrivateKey and a PublicKey"
+            )
+        self._shared_key = nacl.bindings.crypto_box_beforenm(
+            public_key.encode(encoder=encoding.RawEncoder),
+            private_key.encode(encoder=encoding.RawEncoder),
+        )
+
+    def __bytes__(self) -> bytes:
+        return self._shared_key
+
+    @classmethod
+    def decode(
+        cls: Type[_Box], encoded: bytes, encoder: Encoder = encoding.RawEncoder
+    ) -> _Box:
+        """
+        Alternative constructor. Creates a Box from an existing Box's shared key.
+        """
+        # Create an empty box
+        box: _Box = cls.__new__(cls)
+
+        # Assign our decoded value to the shared key of the box
+        box._shared_key = encoder.decode(encoded)
+
+        return box
+
+    def encrypt(
+        self,
+        plaintext: bytes,
+        nonce: Optional[bytes] = None,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> EncryptedMessage:
+        """
+        Encrypts the plaintext message using the given `nonce` (or generates
+        one randomly if omitted) and returns the ciphertext encoded with the
+        encoder.
+
+        .. warning:: It is **VITALLY** important that the nonce is a nonce,
+            i.e. it is a number used only once for any given key. If you fail
+            to do this, you compromise the privacy of the messages encrypted.
+
+        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
+        :param nonce: [:class:`bytes`] The nonce to use in the encryption
+        :param encoder: The encoder to use to encode the ciphertext
+        :rtype: [:class:`nacl.utils.EncryptedMessage`]
+        """
+        if nonce is None:
+            nonce = random(self.NONCE_SIZE)
+
+        if len(nonce) != self.NONCE_SIZE:
+            raise exc.ValueError(
+                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE
+            )
+
+        ciphertext = nacl.bindings.crypto_box_afternm(
+            plaintext,
+            nonce,
+            self._shared_key,
+        )
+
+        encoded_nonce = encoder.encode(nonce)
+        encoded_ciphertext = encoder.encode(ciphertext)
+
+        return EncryptedMessage._from_parts(
+            encoded_nonce,
+            encoded_ciphertext,
+            encoder.encode(nonce + ciphertext),
+        )
+
+    def decrypt(
+        self,
+        ciphertext: bytes,
+        nonce: Optional[bytes] = None,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> bytes:
+        """
+        Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
+        parameter or implicitly, when omitted, as part of the ciphertext) and
+        returns the plaintext message.
+
+        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
+        :param nonce: [:class:`bytes`] The nonce used when encrypting the
+            ciphertext
+        :param encoder: The encoder used to decode the ciphertext.
+        :rtype: [:class:`bytes`]
+        """
+        # Decode our ciphertext
+        ciphertext = encoder.decode(ciphertext)
+
+        if nonce is None:
+            # If we were given the nonce and ciphertext combined, split them.
+            nonce = ciphertext[: self.NONCE_SIZE]
+            ciphertext = ciphertext[self.NONCE_SIZE :]
+
+        if len(nonce) != self.NONCE_SIZE:
+            raise exc.ValueError(
+                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE
+            )
+
+        plaintext = nacl.bindings.crypto_box_open_afternm(
+            ciphertext,
+            nonce,
+            self._shared_key,
+        )
+
+        return plaintext
+
+    def shared_key(self) -> bytes:
+        """
+        Returns the Curve25519 shared secret, that can then be used as a key in
+        other symmetric ciphers.
+
+        .. warning:: It is **VITALLY** important that you use a nonce with your
+            symmetric cipher. If you fail to do this, you compromise the
+            privacy of the messages encrypted. Ensure that the key length of
+            your cipher is 32 bytes.
+        :rtype: [:class:`bytes`]
+        """
+
+        return self._shared_key
+
+
+_Key = TypeVar("_Key", PublicKey, PrivateKey)
+
+
+class SealedBox(Generic[_Key], encoding.Encodable, StringFixer):
+    """
+    The SealedBox class boxes and unboxes messages addressed to
+    a specified key-pair by using ephemeral sender's keypairs,
+    whose private part will be discarded just after encrypting
+    a single plaintext message.
+
+    The ciphertexts generated by :class:`~nacl.public.SecretBox` include
+    the public part of the ephemeral key before the :class:`~nacl.public.Box`
+    ciphertext.
+
+    :param recipient_key: a :class:`~nacl.public.PublicKey` used to encrypt
+        messages and derive nonces, or a :class:`~nacl.public.PrivateKey` used
+        to decrypt messages.
+
+    .. versionadded:: 1.2
+    """
+
+    _public_key: bytes
+    _private_key: Optional[bytes]
+
+    def __init__(self, recipient_key: _Key):
+        if isinstance(recipient_key, PublicKey):
+            self._public_key = recipient_key.encode(
+                encoder=encoding.RawEncoder
+            )
+            self._private_key = None
+        elif isinstance(recipient_key, PrivateKey):
+            self._private_key = recipient_key.encode(
+                encoder=encoding.RawEncoder
+            )
+            self._public_key = recipient_key.public_key.encode(
+                encoder=encoding.RawEncoder
+            )
+        else:
+            raise exc.TypeError(
+                "SealedBox must be created from a PublicKey or a PrivateKey"
+            )
+
+    def __bytes__(self) -> bytes:
+        return self._public_key
+
+    def encrypt(
+        self,
+        plaintext: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> bytes:
+        """
+        Encrypts the plaintext message using a random-generated ephemeral
+        keypair and returns a "composed ciphertext", containing both
+        the public part of the keypair and the ciphertext proper,
+        encoded with the encoder.
+
+        The private part of the ephemeral key-pair will be scrubbed before
+        returning the ciphertext, therefore, the sender will not be able to
+        decrypt the generated ciphertext.
+
+        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
+        :param encoder: The encoder to use to encode the ciphertext
+        :return bytes: encoded ciphertext
+        """
+
+        ciphertext = nacl.bindings.crypto_box_seal(plaintext, self._public_key)
+
+        encoded_ciphertext = encoder.encode(ciphertext)
+
+        return encoded_ciphertext
+
+    def decrypt(
+        self: "SealedBox[PrivateKey]",
+        ciphertext: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> bytes:
+        """
+        Decrypts the ciphertext using the ephemeral public key enclosed
+        in the ciphertext and the SealedBox private key, returning
+        the plaintext message.
+
+        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
+        :param encoder: The encoder used to decode the ciphertext.
+        :return bytes: The original plaintext
+        :raises TypeError: if this SealedBox was created with a
+            :class:`~nacl.public.PublicKey` rather than a
+            :class:`~nacl.public.PrivateKey`.
+        """
+        # Decode our ciphertext
+        ciphertext = encoder.decode(ciphertext)
+
+        if self._private_key is None:
+            raise TypeError(
+                "SealedBoxes created with a public key cannot decrypt"
+            )
+        plaintext = nacl.bindings.crypto_box_seal_open(
+            ciphertext,
+            self._public_key,
+            self._private_key,
+        )
+
+        return plaintext
diff --git a/.venv/lib/python3.12/site-packages/nacl/pwhash/__init__.py b/.venv/lib/python3.12/site-packages/nacl/pwhash/__init__.py
new file mode 100644
index 00000000..ffd76a64
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/pwhash/__init__.py
@@ -0,0 +1,75 @@
+# Copyright 2017 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+from nacl.exceptions import CryptPrefixError
+
+from . import _argon2, argon2i, argon2id, scrypt
+
+STRPREFIX = argon2id.STRPREFIX
+
+PWHASH_SIZE = argon2id.PWHASH_SIZE
+
+assert _argon2.ALG_ARGON2_DEFAULT == _argon2.ALG_ARGON2ID13
+# since version 1.0.15 of libsodium
+
+PASSWD_MIN = argon2id.PASSWD_MIN
+PASSWD_MAX = argon2id.PASSWD_MAX
+MEMLIMIT_MAX = argon2id.MEMLIMIT_MAX
+MEMLIMIT_MIN = argon2id.MEMLIMIT_MIN
+OPSLIMIT_MAX = argon2id.OPSLIMIT_MAX
+OPSLIMIT_MIN = argon2id.OPSLIMIT_MIN
+OPSLIMIT_INTERACTIVE = argon2id.OPSLIMIT_INTERACTIVE
+MEMLIMIT_INTERACTIVE = argon2id.MEMLIMIT_INTERACTIVE
+OPSLIMIT_MODERATE = argon2id.OPSLIMIT_MODERATE
+MEMLIMIT_MODERATE = argon2id.MEMLIMIT_MODERATE
+OPSLIMIT_SENSITIVE = argon2id.OPSLIMIT_SENSITIVE
+MEMLIMIT_SENSITIVE = argon2id.MEMLIMIT_SENSITIVE
+
+str = argon2id.str
+
+assert argon2i.ALG != argon2id.ALG
+
+SCRYPT_SALTBYTES = scrypt.SALTBYTES
+SCRYPT_PWHASH_SIZE = scrypt.PWHASH_SIZE
+SCRYPT_OPSLIMIT_INTERACTIVE = scrypt.OPSLIMIT_INTERACTIVE
+SCRYPT_MEMLIMIT_INTERACTIVE = scrypt.MEMLIMIT_INTERACTIVE
+SCRYPT_OPSLIMIT_SENSITIVE = scrypt.OPSLIMIT_SENSITIVE
+SCRYPT_MEMLIMIT_SENSITIVE = scrypt.MEMLIMIT_SENSITIVE
+
+
+kdf_scryptsalsa208sha256 = scrypt.kdf
+scryptsalsa208sha256_str = scrypt.str
+verify_scryptsalsa208sha256 = scrypt.verify
+
+
+def verify(password_hash: bytes, password: bytes) -> bool:
+    """
+    Takes a modular crypt encoded stored password hash derived using one
+    of the algorithms supported by `libsodium` and checks if the user provided
+    password will hash to the same string when using the parameters saved
+    in the stored hash
+    """
+    if password_hash.startswith(argon2id.STRPREFIX):
+        return argon2id.verify(password_hash, password)
+    elif password_hash.startswith(argon2i.STRPREFIX):
+        return argon2id.verify(password_hash, password)
+    elif scrypt.AVAILABLE and password_hash.startswith(scrypt.STRPREFIX):
+        return scrypt.verify(password_hash, password)
+    else:
+        raise (
+            CryptPrefixError(
+                "given password_hash is not in a supported format"
+            )
+        )
diff --git a/.venv/lib/python3.12/site-packages/nacl/pwhash/_argon2.py b/.venv/lib/python3.12/site-packages/nacl/pwhash/_argon2.py
new file mode 100644
index 00000000..856eda04
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/pwhash/_argon2.py
@@ -0,0 +1,49 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import nacl.bindings
+
+_argon2_strbytes_plus_one = nacl.bindings.crypto_pwhash_STRBYTES
+
+PWHASH_SIZE = _argon2_strbytes_plus_one - 1
+SALTBYTES = nacl.bindings.crypto_pwhash_SALTBYTES
+
+PASSWD_MIN = nacl.bindings.crypto_pwhash_PASSWD_MIN
+PASSWD_MAX = nacl.bindings.crypto_pwhash_PASSWD_MAX
+
+PWHASH_SIZE = _argon2_strbytes_plus_one - 1
+
+BYTES_MAX = nacl.bindings.crypto_pwhash_BYTES_MAX
+BYTES_MIN = nacl.bindings.crypto_pwhash_BYTES_MIN
+
+ALG_ARGON2I13 = nacl.bindings.crypto_pwhash_ALG_ARGON2I13
+ALG_ARGON2ID13 = nacl.bindings.crypto_pwhash_ALG_ARGON2ID13
+ALG_ARGON2_DEFAULT = nacl.bindings.crypto_pwhash_ALG_DEFAULT
+
+
+def verify(password_hash: bytes, password: bytes) -> bool:
+    """
+    Takes a modular crypt encoded argon2i or argon2id stored password hash
+    and checks if the user provided password will hash to the same string
+    when using the stored parameters
+
+    :param password_hash: password hash serialized in modular crypt() format
+    :type password_hash: bytes
+    :param password: user provided password
+    :type password: bytes
+    :rtype: boolean
+
+    .. versionadded:: 1.2
+    """
+    return nacl.bindings.crypto_pwhash_str_verify(password_hash, password)
diff --git a/.venv/lib/python3.12/site-packages/nacl/pwhash/argon2i.py b/.venv/lib/python3.12/site-packages/nacl/pwhash/argon2i.py
new file mode 100644
index 00000000..f9b3af7f
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/pwhash/argon2i.py
@@ -0,0 +1,132 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import nacl.bindings
+import nacl.encoding
+
+from . import _argon2
+
+ALG = _argon2.ALG_ARGON2I13
+STRPREFIX = nacl.bindings.crypto_pwhash_argon2i_STRPREFIX
+
+SALTBYTES = _argon2.SALTBYTES
+
+PASSWD_MIN = _argon2.PASSWD_MIN
+PASSWD_MAX = _argon2.PASSWD_MAX
+
+PWHASH_SIZE = _argon2.PWHASH_SIZE
+
+BYTES_MIN = _argon2.BYTES_MIN
+BYTES_MAX = _argon2.BYTES_MAX
+
+verify = _argon2.verify
+
+MEMLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_MAX
+MEMLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_MIN
+OPSLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_MAX
+OPSLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_MIN
+
+OPSLIMIT_INTERACTIVE = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE
+MEMLIMIT_INTERACTIVE = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE
+OPSLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE
+MEMLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE
+
+OPSLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2i_OPSLIMIT_MODERATE
+MEMLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2i_MEMLIMIT_MODERATE
+
+
+def kdf(
+    size: int,
+    password: bytes,
+    salt: bytes,
+    opslimit: int = OPSLIMIT_SENSITIVE,
+    memlimit: int = MEMLIMIT_SENSITIVE,
+    encoder: nacl.encoding.Encoder = nacl.encoding.RawEncoder,
+) -> bytes:
+    """
+    Derive a ``size`` bytes long key from a caller-supplied
+    ``password`` and ``salt`` pair using the argon2i
+    memory-hard construct.
+
+    the enclosing module provides the constants
+
+        - :py:const:`.OPSLIMIT_INTERACTIVE`
+        - :py:const:`.MEMLIMIT_INTERACTIVE`
+        - :py:const:`.OPSLIMIT_MODERATE`
+        - :py:const:`.MEMLIMIT_MODERATE`
+        - :py:const:`.OPSLIMIT_SENSITIVE`
+        - :py:const:`.MEMLIMIT_SENSITIVE`
+
+    as a guidance for correct settings.
+
+    :param size: derived key size, must be between
+                 :py:const:`.BYTES_MIN` and
+                 :py:const:`.BYTES_MAX`
+    :type size: int
+    :param password: password used to seed the key derivation procedure;
+                     it length must be between
+                     :py:const:`.PASSWD_MIN` and
+                     :py:const:`.PASSWD_MAX`
+    :type password: bytes
+    :param salt: **RANDOM** salt used in the key derivation procedure;
+                 its length must be exactly :py:const:`.SALTBYTES`
+    :type salt: bytes
+    :param opslimit: the time component (operation count)
+                     of the key derivation procedure's computational cost;
+                     it must be between
+                     :py:const:`.OPSLIMIT_MIN` and
+                     :py:const:`.OPSLIMIT_MAX`
+    :type opslimit: int
+    :param memlimit: the memory occupation component
+                     of the key derivation procedure's computational cost;
+                     it must be between
+                     :py:const:`.MEMLIMIT_MIN` and
+                     :py:const:`.MEMLIMIT_MAX`
+    :type memlimit: int
+    :rtype: bytes
+
+    .. versionadded:: 1.2
+    """
+
+    return encoder.encode(
+        nacl.bindings.crypto_pwhash_alg(
+            size, password, salt, opslimit, memlimit, ALG
+        )
+    )
+
+
+def str(
+    password: bytes,
+    opslimit: int = OPSLIMIT_INTERACTIVE,
+    memlimit: int = MEMLIMIT_INTERACTIVE,
+) -> bytes:
+    """
+    Hashes a password with a random salt, using the memory-hard
+    argon2i construct and returning an ascii string that has all
+    the needed info to check against a future password
+
+
+    The default settings for opslimit and memlimit are those deemed
+    correct for the interactive user login case.
+
+    :param bytes password:
+    :param int opslimit:
+    :param int memlimit:
+    :rtype: bytes
+
+    .. versionadded:: 1.2
+    """
+    return nacl.bindings.crypto_pwhash_str_alg(
+        password, opslimit, memlimit, ALG
+    )
diff --git a/.venv/lib/python3.12/site-packages/nacl/pwhash/argon2id.py b/.venv/lib/python3.12/site-packages/nacl/pwhash/argon2id.py
new file mode 100644
index 00000000..1b86d69e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/pwhash/argon2id.py
@@ -0,0 +1,135 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import nacl.bindings
+import nacl.encoding
+
+from . import _argon2
+
+ALG = _argon2.ALG_ARGON2ID13
+STRPREFIX = nacl.bindings.crypto_pwhash_argon2id_STRPREFIX
+
+SALTBYTES = _argon2.SALTBYTES
+
+PASSWD_MIN = _argon2.PASSWD_MIN
+PASSWD_MAX = _argon2.PASSWD_MAX
+
+PWHASH_SIZE = _argon2.PWHASH_SIZE
+
+BYTES_MIN = _argon2.BYTES_MIN
+BYTES_MAX = _argon2.BYTES_MAX
+
+verify = _argon2.verify
+
+MEMLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_MIN
+MEMLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_MAX
+OPSLIMIT_MIN = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_MIN
+OPSLIMIT_MAX = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_MAX
+
+OPSLIMIT_INTERACTIVE = (
+    nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE
+)
+MEMLIMIT_INTERACTIVE = (
+    nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE
+)
+OPSLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE
+MEMLIMIT_SENSITIVE = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE
+
+OPSLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2id_OPSLIMIT_MODERATE
+MEMLIMIT_MODERATE = nacl.bindings.crypto_pwhash_argon2id_MEMLIMIT_MODERATE
+
+
+def kdf(
+    size: int,
+    password: bytes,
+    salt: bytes,
+    opslimit: int = OPSLIMIT_SENSITIVE,
+    memlimit: int = MEMLIMIT_SENSITIVE,
+    encoder: nacl.encoding.Encoder = nacl.encoding.RawEncoder,
+) -> bytes:
+    """
+    Derive a ``size`` bytes long key from a caller-supplied
+    ``password`` and ``salt`` pair using the argon2i
+    memory-hard construct.
+
+    the enclosing module provides the constants
+
+        - :py:const:`.OPSLIMIT_INTERACTIVE`
+        - :py:const:`.MEMLIMIT_INTERACTIVE`
+        - :py:const:`.OPSLIMIT_MODERATE`
+        - :py:const:`.MEMLIMIT_MODERATE`
+        - :py:const:`.OPSLIMIT_SENSITIVE`
+        - :py:const:`.MEMLIMIT_SENSITIVE`
+
+    as a guidance for correct settings.
+
+    :param size: derived key size, must be between
+                 :py:const:`.BYTES_MIN` and
+                 :py:const:`.BYTES_MAX`
+    :type size: int
+    :param password: password used to seed the key derivation procedure;
+                     it length must be between
+                     :py:const:`.PASSWD_MIN` and
+                     :py:const:`.PASSWD_MAX`
+    :type password: bytes
+    :param salt: **RANDOM** salt used in the key derivation procedure;
+                 its length must be exactly :py:const:`.SALTBYTES`
+    :type salt: bytes
+    :param opslimit: the time component (operation count)
+                     of the key derivation procedure's computational cost;
+                     it must be between
+                     :py:const:`.OPSLIMIT_MIN` and
+                     :py:const:`.OPSLIMIT_MAX`
+    :type opslimit: int
+    :param memlimit: the memory occupation component
+                     of the key derivation procedure's computational cost;
+                     it must be between
+                     :py:const:`.MEMLIMIT_MIN` and
+                     :py:const:`.MEMLIMIT_MAX`
+    :type memlimit: int
+    :rtype: bytes
+
+    .. versionadded:: 1.2
+    """
+
+    return encoder.encode(
+        nacl.bindings.crypto_pwhash_alg(
+            size, password, salt, opslimit, memlimit, ALG
+        )
+    )
+
+
+def str(
+    password: bytes,
+    opslimit: int = OPSLIMIT_INTERACTIVE,
+    memlimit: int = MEMLIMIT_INTERACTIVE,
+) -> bytes:
+    """
+    Hashes a password with a random salt, using the memory-hard
+    argon2id construct and returning an ascii string that has all
+    the needed info to check against a future password
+
+    The default settings for opslimit and memlimit are those deemed
+    correct for the interactive user login case.
+
+    :param bytes password:
+    :param int opslimit:
+    :param int memlimit:
+    :rtype: bytes
+
+    .. versionadded:: 1.2
+    """
+    return nacl.bindings.crypto_pwhash_str_alg(
+        password, opslimit, memlimit, ALG
+    )
diff --git a/.venv/lib/python3.12/site-packages/nacl/pwhash/scrypt.py b/.venv/lib/python3.12/site-packages/nacl/pwhash/scrypt.py
new file mode 100644
index 00000000..55bdf498
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/pwhash/scrypt.py
@@ -0,0 +1,211 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import cast
+
+import nacl.bindings
+import nacl.encoding
+from nacl import exceptions as exc
+from nacl.exceptions import ensure
+
+_strbytes_plus_one = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRBYTES
+
+AVAILABLE = nacl.bindings.has_crypto_pwhash_scryptsalsa208sha256
+
+STRPREFIX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRPREFIX
+
+SALTBYTES = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_SALTBYTES
+
+PASSWD_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN
+PASSWD_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX
+
+PWHASH_SIZE = _strbytes_plus_one - 1
+
+BYTES_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_BYTES_MIN
+BYTES_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_BYTES_MAX
+
+MEMLIMIT_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN
+MEMLIMIT_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX
+OPSLIMIT_MIN = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN
+OPSLIMIT_MAX = nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX
+
+OPSLIMIT_INTERACTIVE = (
+    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
+)
+MEMLIMIT_INTERACTIVE = (
+    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
+)
+OPSLIMIT_SENSITIVE = (
+    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE
+)
+MEMLIMIT_SENSITIVE = (
+    nacl.bindings.crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE
+)
+
+OPSLIMIT_MODERATE = 8 * OPSLIMIT_INTERACTIVE
+MEMLIMIT_MODERATE = 8 * MEMLIMIT_INTERACTIVE
+
+
+def kdf(
+    size: int,
+    password: bytes,
+    salt: bytes,
+    opslimit: int = OPSLIMIT_SENSITIVE,
+    memlimit: int = MEMLIMIT_SENSITIVE,
+    encoder: nacl.encoding.Encoder = nacl.encoding.RawEncoder,
+) -> bytes:
+    """
+    Derive a ``size`` bytes long key from a caller-supplied
+    ``password`` and ``salt`` pair using the scryptsalsa208sha256
+    memory-hard construct.
+
+
+    the enclosing module provides the constants
+
+        - :py:const:`.OPSLIMIT_INTERACTIVE`
+        - :py:const:`.MEMLIMIT_INTERACTIVE`
+        - :py:const:`.OPSLIMIT_SENSITIVE`
+        - :py:const:`.MEMLIMIT_SENSITIVE`
+        - :py:const:`.OPSLIMIT_MODERATE`
+        - :py:const:`.MEMLIMIT_MODERATE`
+
+    as a guidance for correct settings respectively for the
+    interactive login and the long term key protecting sensitive data
+    use cases.
+
+    :param size: derived key size, must be between
+                 :py:const:`.BYTES_MIN` and
+                 :py:const:`.BYTES_MAX`
+    :type size: int
+    :param password: password used to seed the key derivation procedure;
+                     it length must be between
+                     :py:const:`.PASSWD_MIN` and
+                     :py:const:`.PASSWD_MAX`
+    :type password: bytes
+    :param salt: **RANDOM** salt used in the key derivation procedure;
+                 its length must be exactly :py:const:`.SALTBYTES`
+    :type salt: bytes
+    :param opslimit: the time component (operation count)
+                     of the key derivation procedure's computational cost;
+                     it must be between
+                     :py:const:`.OPSLIMIT_MIN` and
+                     :py:const:`.OPSLIMIT_MAX`
+    :type opslimit: int
+    :param memlimit: the memory occupation component
+                     of the key derivation procedure's computational cost;
+                     it must be between
+                     :py:const:`.MEMLIMIT_MIN` and
+                     :py:const:`.MEMLIMIT_MAX`
+    :type memlimit: int
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+
+    .. versionadded:: 1.2
+    """
+    ensure(
+        AVAILABLE,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        len(salt) == SALTBYTES,
+        "The salt must be exactly %s, not %s bytes long"
+        % (SALTBYTES, len(salt)),
+        raising=exc.ValueError,
+    )
+
+    n_log2, r, p = nacl.bindings.nacl_bindings_pick_scrypt_params(
+        opslimit, memlimit
+    )
+    maxmem = memlimit + (2 ** 16)
+
+    return encoder.encode(
+        nacl.bindings.crypto_pwhash_scryptsalsa208sha256_ll(
+            password,
+            salt,
+            # Cast safety: n_log2 is a positive integer, and so 2 ** n_log2 is also
+            # a positive integer. Mypy+typeshed can't deduce this, because there's no
+            # way to for them to know that n_log2: int is positive.
+            cast(int, 2 ** n_log2),
+            r,
+            p,
+            maxmem=maxmem,
+            dklen=size,
+        )
+    )
+
+
+def str(
+    password: bytes,
+    opslimit: int = OPSLIMIT_INTERACTIVE,
+    memlimit: int = MEMLIMIT_INTERACTIVE,
+) -> bytes:
+    """
+    Hashes a password with a random salt, using the memory-hard
+    scryptsalsa208sha256 construct and returning an ascii string
+    that has all the needed info to check against a future password
+
+    The default settings for opslimit and memlimit are those deemed
+    correct for the interactive user login case.
+
+    :param bytes password:
+    :param int opslimit:
+    :param int memlimit:
+    :rtype: bytes
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+
+    .. versionadded:: 1.2
+    """
+    ensure(
+        AVAILABLE,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str(
+        password, opslimit, memlimit
+    )
+
+
+def verify(password_hash: bytes, password: bytes) -> bool:
+    """
+    Takes the output of scryptsalsa208sha256 and compares it against
+    a user provided password to see if they are the same
+
+    :param password_hash: bytes
+    :param password: bytes
+    :rtype: boolean
+    :raises nacl.exceptions.UnavailableError: If called when using a
+        minimal build of libsodium.
+
+    .. versionadded:: 1.2
+    """
+    ensure(
+        AVAILABLE,
+        "Not available in minimal build",
+        raising=exc.UnavailableError,
+    )
+
+    ensure(
+        len(password_hash) == PWHASH_SIZE,
+        "The password hash must be exactly %s bytes long"
+        % nacl.bindings.crypto_pwhash_scryptsalsa208sha256_STRBYTES,
+        raising=exc.ValueError,
+    )
+
+    return nacl.bindings.crypto_pwhash_scryptsalsa208sha256_str_verify(
+        password_hash, password
+    )
diff --git a/.venv/lib/python3.12/site-packages/nacl/py.typed b/.venv/lib/python3.12/site-packages/nacl/py.typed
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/py.typed
diff --git a/.venv/lib/python3.12/site-packages/nacl/secret.py b/.venv/lib/python3.12/site-packages/nacl/secret.py
new file mode 100644
index 00000000..ba536a27
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/secret.py
@@ -0,0 +1,305 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import ClassVar, Optional
+
+import nacl.bindings
+from nacl import encoding
+from nacl import exceptions as exc
+from nacl.utils import EncryptedMessage, StringFixer, random
+
+
+class SecretBox(encoding.Encodable, StringFixer):
+    """
+    The SecretBox class encrypts and decrypts messages using the given secret
+    key.
+
+    The ciphertexts generated by :class:`~nacl.secret.Secretbox` include a 16
+    byte authenticator which is checked as part of the decryption. An invalid
+    authenticator will cause the decrypt function to raise an exception. The
+    authenticator is not a signature. Once you've decrypted the message you've
+    demonstrated the ability to create arbitrary valid message, so messages you
+    send are repudiable. For non-repudiable messages, sign them after
+    encryption.
+
+    Encryption is done using `XSalsa20-Poly1305`_, and there are no practical
+    limits on the number or size of messages (up to 2⁶⁴ messages, each up to 2⁶⁴
+    bytes).
+
+    .. _XSalsa20-Poly1305: https://doc.libsodium.org/secret-key_cryptography/secretbox#algorithm-details
+
+    :param key: The secret key used to encrypt and decrypt messages
+    :param encoder: The encoder class used to decode the given key
+
+    :cvar KEY_SIZE: The size that the key is required to be.
+    :cvar NONCE_SIZE: The size that the nonce is required to be.
+    :cvar MACBYTES: The size of the authentication MAC tag in bytes.
+    :cvar MESSAGEBYTES_MAX: The maximum size of a message which can be
+                            safely encrypted with a single key/nonce
+                            pair.
+    """
+
+    KEY_SIZE: ClassVar[int] = nacl.bindings.crypto_secretbox_KEYBYTES
+    NONCE_SIZE: ClassVar[int] = nacl.bindings.crypto_secretbox_NONCEBYTES
+    MACBYTES: ClassVar[int] = nacl.bindings.crypto_secretbox_MACBYTES
+    MESSAGEBYTES_MAX: ClassVar[
+        int
+    ] = nacl.bindings.crypto_secretbox_MESSAGEBYTES_MAX
+
+    def __init__(
+        self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
+    ):
+        key = encoder.decode(key)
+        if not isinstance(key, bytes):
+            raise exc.TypeError("SecretBox must be created from 32 bytes")
+
+        if len(key) != self.KEY_SIZE:
+            raise exc.ValueError(
+                "The key must be exactly %s bytes long" % self.KEY_SIZE,
+            )
+
+        self._key = key
+
+    def __bytes__(self) -> bytes:
+        return self._key
+
+    def encrypt(
+        self,
+        plaintext: bytes,
+        nonce: Optional[bytes] = None,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> EncryptedMessage:
+        """
+        Encrypts the plaintext message using the given `nonce` (or generates
+        one randomly if omitted) and returns the ciphertext encoded with the
+        encoder.
+
+        .. warning:: It is **VITALLY** important that the nonce is a nonce,
+            i.e. it is a number used only once for any given key. If you fail
+            to do this, you compromise the privacy of the messages encrypted.
+            Give your nonces a different prefix, or have one side use an odd
+            counter and one an even counter. Just make sure they are different.
+
+        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
+        :param nonce: [:class:`bytes`] The nonce to use in the encryption
+        :param encoder: The encoder to use to encode the ciphertext
+        :rtype: [:class:`nacl.utils.EncryptedMessage`]
+        """
+        if nonce is None:
+            nonce = random(self.NONCE_SIZE)
+
+        if len(nonce) != self.NONCE_SIZE:
+            raise exc.ValueError(
+                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
+            )
+
+        ciphertext = nacl.bindings.crypto_secretbox(
+            plaintext, nonce, self._key
+        )
+
+        encoded_nonce = encoder.encode(nonce)
+        encoded_ciphertext = encoder.encode(ciphertext)
+
+        return EncryptedMessage._from_parts(
+            encoded_nonce,
+            encoded_ciphertext,
+            encoder.encode(nonce + ciphertext),
+        )
+
+    def decrypt(
+        self,
+        ciphertext: bytes,
+        nonce: Optional[bytes] = None,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> bytes:
+        """
+        Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
+        parameter or implicitly, when omitted, as part of the ciphertext) and
+        returns the plaintext message.
+
+        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
+        :param nonce: [:class:`bytes`] The nonce used when encrypting the
+            ciphertext
+        :param encoder: The encoder used to decode the ciphertext.
+        :rtype: [:class:`bytes`]
+        """
+        # Decode our ciphertext
+        ciphertext = encoder.decode(ciphertext)
+
+        if nonce is None:
+            # If we were given the nonce and ciphertext combined, split them.
+            nonce = ciphertext[: self.NONCE_SIZE]
+            ciphertext = ciphertext[self.NONCE_SIZE :]
+
+        if len(nonce) != self.NONCE_SIZE:
+            raise exc.ValueError(
+                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
+            )
+
+        plaintext = nacl.bindings.crypto_secretbox_open(
+            ciphertext, nonce, self._key
+        )
+
+        return plaintext
+
+
+class Aead(encoding.Encodable, StringFixer):
+    """
+    The AEAD class encrypts and decrypts messages using the given secret key.
+
+    Unlike :class:`~nacl.secret.SecretBox`, AEAD supports authenticating
+    non-confidential data received alongside the message, such as a length
+    or type tag.
+
+    Like :class:`~nacl.secret.Secretbox`, this class provides authenticated
+    encryption. An inauthentic message will cause the decrypt function to raise
+    an exception.
+
+    Likewise, the authenticator should not be mistaken for a (public-key)
+    signature: recipients (with the ability to decrypt messages) are capable of
+    creating arbitrary valid message; in particular, this means AEAD messages
+    are repudiable. For non-repudiable messages, sign them after encryption.
+
+    The cryptosystem used is `XChacha20-Poly1305`_ as specified for
+    `standardization`_. There are `no practical limits`_ to how much can safely
+    be encrypted under a given key (up to 2⁶⁴ messages each containing up
+    to 2⁶⁴ bytes).
+
+    .. _standardization: https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-xchacha
+    .. _XChacha20-Poly1305: https://doc.libsodium.org/secret-key_cryptography/aead#xchacha-20-poly1305
+    .. _no practical limits: https://doc.libsodium.org/secret-key_cryptography/aead#limitations
+
+    :param key: The secret key used to encrypt and decrypt messages
+    :param encoder: The encoder class used to decode the given key
+
+    :cvar KEY_SIZE: The size that the key is required to be.
+    :cvar NONCE_SIZE: The size that the nonce is required to be.
+    :cvar MACBYTES: The size of the authentication MAC tag in bytes.
+    :cvar MESSAGEBYTES_MAX: The maximum size of a message which can be
+                            safely encrypted with a single key/nonce
+                            pair.
+    """
+
+    KEY_SIZE = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_KEYBYTES
+    NONCE_SIZE = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_NPUBBYTES
+    MACBYTES = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_ABYTES
+    MESSAGEBYTES_MAX = (
+        nacl.bindings.crypto_aead_xchacha20poly1305_ietf_MESSAGEBYTES_MAX
+    )
+
+    def __init__(
+        self,
+        key: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ):
+        key = encoder.decode(key)
+        if not isinstance(key, bytes):
+            raise exc.TypeError("AEAD must be created from 32 bytes")
+
+        if len(key) != self.KEY_SIZE:
+            raise exc.ValueError(
+                "The key must be exactly %s bytes long" % self.KEY_SIZE,
+            )
+
+        self._key = key
+
+    def __bytes__(self) -> bytes:
+        return self._key
+
+    def encrypt(
+        self,
+        plaintext: bytes,
+        aad: bytes = b"",
+        nonce: Optional[bytes] = None,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> EncryptedMessage:
+        """
+        Encrypts the plaintext message using the given `nonce` (or generates
+        one randomly if omitted) and returns the ciphertext encoded with the
+        encoder.
+
+        .. warning:: It is vitally important for :param nonce: to be unique.
+            By default, it is generated randomly; [:class:`Aead`] uses XChacha20
+            for extended (192b) nonce size, so the risk of reusing random nonces
+            is negligible.  It is *strongly recommended* to keep this behaviour,
+            as nonce reuse will compromise the privacy of encrypted messages.
+            Should implicit nonces be inadequate for your application, the
+            second best option is using split counters; e.g. if sending messages
+            encrypted under a shared key between 2 users, each user can use the
+            number of messages it sent so far, prefixed or suffixed with a 1bit
+            user id.  Note that the counter must **never** be rolled back (due
+            to overflow, on-disk state being rolled back to an earlier backup,
+            ...)
+
+        :param plaintext: [:class:`bytes`] The plaintext message to encrypt
+        :param nonce: [:class:`bytes`] The nonce to use in the encryption
+        :param encoder: The encoder to use to encode the ciphertext
+        :rtype: [:class:`nacl.utils.EncryptedMessage`]
+        """
+        if nonce is None:
+            nonce = random(self.NONCE_SIZE)
+
+        if len(nonce) != self.NONCE_SIZE:
+            raise exc.ValueError(
+                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
+            )
+
+        ciphertext = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_encrypt(
+            plaintext, aad, nonce, self._key
+        )
+
+        encoded_nonce = encoder.encode(nonce)
+        encoded_ciphertext = encoder.encode(ciphertext)
+
+        return EncryptedMessage._from_parts(
+            encoded_nonce,
+            encoded_ciphertext,
+            encoder.encode(nonce + ciphertext),
+        )
+
+    def decrypt(
+        self,
+        ciphertext: bytes,
+        aad: bytes = b"",
+        nonce: Optional[bytes] = None,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> bytes:
+        """
+        Decrypts the ciphertext using the `nonce` (explicitly, when passed as a
+        parameter or implicitly, when omitted, as part of the ciphertext) and
+        returns the plaintext message.
+
+        :param ciphertext: [:class:`bytes`] The encrypted message to decrypt
+        :param nonce: [:class:`bytes`] The nonce used when encrypting the
+            ciphertext
+        :param encoder: The encoder used to decode the ciphertext.
+        :rtype: [:class:`bytes`]
+        """
+        # Decode our ciphertext
+        ciphertext = encoder.decode(ciphertext)
+
+        if nonce is None:
+            # If we were given the nonce and ciphertext combined, split them.
+            nonce = ciphertext[: self.NONCE_SIZE]
+            ciphertext = ciphertext[self.NONCE_SIZE :]
+
+        if len(nonce) != self.NONCE_SIZE:
+            raise exc.ValueError(
+                "The nonce must be exactly %s bytes long" % self.NONCE_SIZE,
+            )
+
+        plaintext = nacl.bindings.crypto_aead_xchacha20poly1305_ietf_decrypt(
+            ciphertext, aad, nonce, self._key
+        )
+
+        return plaintext
diff --git a/.venv/lib/python3.12/site-packages/nacl/signing.py b/.venv/lib/python3.12/site-packages/nacl/signing.py
new file mode 100644
index 00000000..12ec8ec4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/signing.py
@@ -0,0 +1,250 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from typing import Optional
+
+import nacl.bindings
+from nacl import encoding
+from nacl import exceptions as exc
+from nacl.public import (
+    PrivateKey as _Curve25519_PrivateKey,
+    PublicKey as _Curve25519_PublicKey,
+)
+from nacl.utils import StringFixer, random
+
+
+class SignedMessage(bytes):
+    """
+    A bytes subclass that holds a messaged that has been signed by a
+    :class:`SigningKey`.
+    """
+
+    _signature: bytes
+    _message: bytes
+
+    @classmethod
+    def _from_parts(
+        cls, signature: bytes, message: bytes, combined: bytes
+    ) -> "SignedMessage":
+        obj = cls(combined)
+        obj._signature = signature
+        obj._message = message
+        return obj
+
+    @property
+    def signature(self) -> bytes:
+        """
+        The signature contained within the :class:`SignedMessage`.
+        """
+        return self._signature
+
+    @property
+    def message(self) -> bytes:
+        """
+        The message contained within the :class:`SignedMessage`.
+        """
+        return self._message
+
+
+class VerifyKey(encoding.Encodable, StringFixer):
+    """
+    The public key counterpart to an Ed25519 SigningKey for producing digital
+    signatures.
+
+    :param key: [:class:`bytes`] Serialized Ed25519 public key
+    :param encoder: A class that is able to decode the `key`
+    """
+
+    def __init__(
+        self, key: bytes, encoder: encoding.Encoder = encoding.RawEncoder
+    ):
+        # Decode the key
+        key = encoder.decode(key)
+        if not isinstance(key, bytes):
+            raise exc.TypeError("VerifyKey must be created from 32 bytes")
+
+        if len(key) != nacl.bindings.crypto_sign_PUBLICKEYBYTES:
+            raise exc.ValueError(
+                "The key must be exactly %s bytes long"
+                % nacl.bindings.crypto_sign_PUBLICKEYBYTES,
+            )
+
+        self._key = key
+
+    def __bytes__(self) -> bytes:
+        return self._key
+
+    def __hash__(self) -> int:
+        return hash(bytes(self))
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, self.__class__):
+            return False
+        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
+
+    def __ne__(self, other: object) -> bool:
+        return not (self == other)
+
+    def verify(
+        self,
+        smessage: bytes,
+        signature: Optional[bytes] = None,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> bytes:
+        """
+        Verifies the signature of a signed message, returning the message
+        if it has not been tampered with else raising
+        :class:`~nacl.signing.BadSignatureError`.
+
+        :param smessage: [:class:`bytes`] Either the original messaged or a
+            signature and message concated together.
+        :param signature: [:class:`bytes`] If an unsigned message is given for
+            smessage then the detached signature must be provided.
+        :param encoder: A class that is able to decode the secret message and
+            signature.
+        :rtype: :class:`bytes`
+        """
+        if signature is not None:
+            # If we were given the message and signature separately, validate
+            #   signature size and combine them.
+            if not isinstance(signature, bytes):
+                raise exc.TypeError(
+                    "Verification signature must be created from %d bytes"
+                    % nacl.bindings.crypto_sign_BYTES,
+                )
+
+            if len(signature) != nacl.bindings.crypto_sign_BYTES:
+                raise exc.ValueError(
+                    "The signature must be exactly %d bytes long"
+                    % nacl.bindings.crypto_sign_BYTES,
+                )
+
+            smessage = signature + encoder.decode(smessage)
+        else:
+            # Decode the signed message
+            smessage = encoder.decode(smessage)
+
+        return nacl.bindings.crypto_sign_open(smessage, self._key)
+
+    def to_curve25519_public_key(self) -> _Curve25519_PublicKey:
+        """
+        Converts a :class:`~nacl.signing.VerifyKey` to a
+        :class:`~nacl.public.PublicKey`
+
+        :rtype: :class:`~nacl.public.PublicKey`
+        """
+        raw_pk = nacl.bindings.crypto_sign_ed25519_pk_to_curve25519(self._key)
+        return _Curve25519_PublicKey(raw_pk)
+
+
+class SigningKey(encoding.Encodable, StringFixer):
+    """
+    Private key for producing digital signatures using the Ed25519 algorithm.
+
+    Signing keys are produced from a 32-byte (256-bit) random seed value. This
+    value can be passed into the :class:`~nacl.signing.SigningKey` as a
+    :func:`bytes` whose length is 32.
+
+    .. warning:: This **must** be protected and remain secret. Anyone who knows
+        the value of your :class:`~nacl.signing.SigningKey` or it's seed can
+        masquerade as you.
+
+    :param seed: [:class:`bytes`] Random 32-byte value (i.e. private key)
+    :param encoder: A class that is able to decode the seed
+
+    :ivar: verify_key: [:class:`~nacl.signing.VerifyKey`] The verify
+        (i.e. public) key that corresponds with this signing key.
+    """
+
+    def __init__(
+        self,
+        seed: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ):
+        # Decode the seed
+        seed = encoder.decode(seed)
+        if not isinstance(seed, bytes):
+            raise exc.TypeError(
+                "SigningKey must be created from a 32 byte seed"
+            )
+
+        # Verify that our seed is the proper size
+        if len(seed) != nacl.bindings.crypto_sign_SEEDBYTES:
+            raise exc.ValueError(
+                "The seed must be exactly %d bytes long"
+                % nacl.bindings.crypto_sign_SEEDBYTES
+            )
+
+        public_key, secret_key = nacl.bindings.crypto_sign_seed_keypair(seed)
+
+        self._seed = seed
+        self._signing_key = secret_key
+        self.verify_key = VerifyKey(public_key)
+
+    def __bytes__(self) -> bytes:
+        return self._seed
+
+    def __hash__(self) -> int:
+        return hash(bytes(self))
+
+    def __eq__(self, other: object) -> bool:
+        if not isinstance(other, self.__class__):
+            return False
+        return nacl.bindings.sodium_memcmp(bytes(self), bytes(other))
+
+    def __ne__(self, other: object) -> bool:
+        return not (self == other)
+
+    @classmethod
+    def generate(cls) -> "SigningKey":
+        """
+        Generates a random :class:`~nacl.signing.SigningKey` object.
+
+        :rtype: :class:`~nacl.signing.SigningKey`
+        """
+        return cls(
+            random(nacl.bindings.crypto_sign_SEEDBYTES),
+            encoder=encoding.RawEncoder,
+        )
+
+    def sign(
+        self,
+        message: bytes,
+        encoder: encoding.Encoder = encoding.RawEncoder,
+    ) -> SignedMessage:
+        """
+        Sign a message using this key.
+
+        :param message: [:class:`bytes`] The data to be signed.
+        :param encoder: A class that is used to encode the signed message.
+        :rtype: :class:`~nacl.signing.SignedMessage`
+        """
+        raw_signed = nacl.bindings.crypto_sign(message, self._signing_key)
+
+        crypto_sign_BYTES = nacl.bindings.crypto_sign_BYTES
+        signature = encoder.encode(raw_signed[:crypto_sign_BYTES])
+        message = encoder.encode(raw_signed[crypto_sign_BYTES:])
+        signed = encoder.encode(raw_signed)
+
+        return SignedMessage._from_parts(signature, message, signed)
+
+    def to_curve25519_private_key(self) -> _Curve25519_PrivateKey:
+        """
+        Converts a :class:`~nacl.signing.SigningKey` to a
+        :class:`~nacl.public.PrivateKey`
+
+        :rtype: :class:`~nacl.public.PrivateKey`
+        """
+        sk = self._signing_key
+        raw_private = nacl.bindings.crypto_sign_ed25519_sk_to_curve25519(sk)
+        return _Curve25519_PrivateKey(raw_private)
diff --git a/.venv/lib/python3.12/site-packages/nacl/utils.py b/.venv/lib/python3.12/site-packages/nacl/utils.py
new file mode 100644
index 00000000..d19d236a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/nacl/utils.py
@@ -0,0 +1,88 @@
+# Copyright 2013 Donald Stufft and individual contributors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+
+import os
+from typing import SupportsBytes, Type, TypeVar
+
+import nacl.bindings
+from nacl import encoding
+
+_EncryptedMessage = TypeVar("_EncryptedMessage", bound="EncryptedMessage")
+
+
+class EncryptedMessage(bytes):
+    """
+    A bytes subclass that holds a messaged that has been encrypted by a
+    :class:`SecretBox`.
+    """
+
+    _nonce: bytes
+    _ciphertext: bytes
+
+    @classmethod
+    def _from_parts(
+        cls: Type[_EncryptedMessage],
+        nonce: bytes,
+        ciphertext: bytes,
+        combined: bytes,
+    ) -> _EncryptedMessage:
+        obj = cls(combined)
+        obj._nonce = nonce
+        obj._ciphertext = ciphertext
+        return obj
+
+    @property
+    def nonce(self) -> bytes:
+        """
+        The nonce used during the encryption of the :class:`EncryptedMessage`.
+        """
+        return self._nonce
+
+    @property
+    def ciphertext(self) -> bytes:
+        """
+        The ciphertext contained within the :class:`EncryptedMessage`.
+        """
+        return self._ciphertext
+
+
+class StringFixer:
+    def __str__(self: SupportsBytes) -> str:
+        return str(self.__bytes__())
+
+
+def bytes_as_string(bytes_in: bytes) -> str:
+    return bytes_in.decode("ascii")
+
+
+def random(size: int = 32) -> bytes:
+    return os.urandom(size)
+
+
+def randombytes_deterministic(
+    size: int, seed: bytes, encoder: encoding.Encoder = encoding.RawEncoder
+) -> bytes:
+    """
+    Returns ``size`` number of deterministically generated pseudorandom bytes
+    from a seed
+
+    :param size: int
+    :param seed: bytes
+    :param encoder: The encoder class used to encode the produced bytes
+    :rtype: bytes
+    """
+    raw_data = nacl.bindings.randombytes_buf_deterministic(size, seed)
+
+    return encoder.encode(raw_data)