Migrate chez-crypto to jerboa-crypto: source -> src/jerboa-crypto.ss (Jerboa .ss, prelude import, def). C shim renamed jerboa_crypto_shim.c with jerboa_* symbol prefix; CHEZ_CRYPTO_LIB -> JERBOA_CRYPTO_LIB. Makefile uses jerbuild + brew openssl@3 on darwin. Tests: 34/34 pass.

ober

a8bceb0203a7b6ecd8cea2218bf1f374d897d0ef

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5381726
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,6 @@
+lib/
+*.so
+*.dylib
+*.wpo
+*.o
+.jerbuild-hashes
diff --git a/Makefile b/Makefile
index e345aeb..7c9ffd5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,17 +1,48 @@
-CC = gcc
-CFLAGS = -shared -fPIC -O2
-LIBS = -lcrypto
-SCHEME = scheme
+JERBOA_HOME ?= $(HOME)/mine/jerboa
+SCHEME      ?= $(JERBOA_HOME)/.chez/bin/scheme
+JERBUILD    ?= $(JERBOA_HOME)/jerbuild
+LIBDIRS      = lib:$(JERBOA_HOME)/lib
 
-.PHONY: all clean test
+CC      ?= cc
 
-all: chez_crypto_shim.so
+# OpenSSL — Linux has it in standard paths; macOS needs brew's openssl@3
+UNAME   := $(shell uname -s)
+ifeq ($(UNAME),Darwin)
+OPENSSL_PREFIX ?= $(shell brew --prefix openssl@3 2>/dev/null)
+ifneq ($(OPENSSL_PREFIX),)
+OPENSSL_CFLAGS := -I$(OPENSSL_PREFIX)/include
+OPENSSL_LIBS   := -L$(OPENSSL_PREFIX)/lib -lcrypto
+else
+OPENSSL_LIBS   := -lcrypto
+endif
+else
+OPENSSL_LIBS   := -lcrypto
+endif
 
-chez_crypto_shim.so: chez_crypto_shim.c
+CFLAGS  ?= -shared -fPIC -O2 $(OPENSSL_CFLAGS)
+LIBS    ?= $(OPENSSL_LIBS)
+SHIM     = jerboa_crypto_shim.so
+
+.PHONY: all build transpile test clean shim
+
+all: build
+
+shim: $(SHIM)
+
+$(SHIM): jerboa_crypto_shim.c
 	$(CC) $(CFLAGS) -o $@ $< $(LIBS)
 
-test: chez_crypto_shim.so
-	LD_LIBRARY_PATH=. $(SCHEME) --libdirs src --script tests/crypto-test.ss
+transpile:
+	$(JERBUILD) transpile src lib --force
+
+build: shim transpile
+
+test: build
+	JERBOA_CRYPTO_LIB=$(CURDIR) \
+	DYLD_LIBRARY_PATH=$(CURDIR) \
+	LD_LIBRARY_PATH=$(CURDIR) \
+	$(SCHEME) --libdirs "$(LIBDIRS)" --script tests/crypto-test.ss
 
 clean:
-	rm -f chez_crypto_shim.so
+	rm -f $(SHIM) chez_crypto_shim.so
+	rm -rf lib
diff --git a/chez_crypto_shim.c b/chez_crypto_shim.c
deleted file mode 100644
index 66d6de8..0000000
--- a/chez_crypto_shim.c
+++ /dev/null
@@ -1,386 +0,0 @@
-/* chez_crypto_shim.c — OpenSSL libcrypto wrapper for Chez Scheme FFI */
-
-#include <openssl/evp.h>
-#include <openssl/hmac.h>
-#include <openssl/bn.h>
-#include <openssl/dh.h>
-#include <openssl/rand.h>
-#include <openssl/err.h>
-#include <openssl/kdf.h>
-#include <openssl/crypto.h>
-#include <stdlib.h>
-#include <string.h>
-
-/* ---- Error handling ---- */
-
-int chez_crypto_err_get(char *buf, int buflen) {
-    unsigned long e = ERR_get_error();
-    if (e == 0) {
-        buf[0] = 0;
-        return 0;
-    }
-    ERR_error_string_n(e, buf, buflen);
-    return 1;
-}
-
-/* ---- Random ---- */
-
-int chez_rand_bytes(unsigned char *buf, int n) {
-    return RAND_bytes(buf, n);
-}
-
-/* ---- Digest (Hash) ---- */
-
-void *chez_digest_ctx_new(void) {
-    return EVP_MD_CTX_new();
-}
-
-void chez_digest_ctx_free(void *ctx) {
-    EVP_MD_CTX_free((EVP_MD_CTX *)ctx);
-}
-
-int chez_digest_init(void *ctx, const char *algo) {
-    const EVP_MD *md = EVP_get_digestbyname(algo);
-    if (!md) return -1;
-    return EVP_DigestInit_ex((EVP_MD_CTX *)ctx, md, NULL) == 1 ? 0 : -2;
-}
-
-int chez_digest_update(void *ctx, const unsigned char *data, int len) {
-    return EVP_DigestUpdate((EVP_MD_CTX *)ctx, data, len) == 1 ? 0 : -1;
-}
-
-int chez_digest_final(void *ctx, unsigned char *out, int *outlen) {
-    unsigned int len = 0;
-    int rc = EVP_DigestFinal_ex((EVP_MD_CTX *)ctx, out, &len);
-    *outlen = (int)len;
-    return rc == 1 ? 0 : -1;
-}
-
-int chez_digest_size(const char *algo) {
-    const EVP_MD *md = EVP_get_digestbyname(algo);
-    if (!md) return -1;
-    return EVP_MD_size(md);
-}
-
-/* One-shot digest */
-int chez_digest(const char *algo, const unsigned char *data, int datalen,
-                unsigned char *out, int *outlen) {
-    const EVP_MD *md = EVP_get_digestbyname(algo);
-    if (!md) return -1;
-    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
-    if (!ctx) return -2;
-    unsigned int len = 0;
-    int rc = -3;
-    if (EVP_DigestInit_ex(ctx, md, NULL) == 1 &&
-        EVP_DigestUpdate(ctx, data, datalen) == 1 &&
-        EVP_DigestFinal_ex(ctx, out, &len) == 1) {
-        rc = 0;
-    }
-    *outlen = (int)len;
-    EVP_MD_CTX_free(ctx);
-    return rc;
-}
-
-/* ---- HMAC ---- */
-
-int chez_hmac(const char *algo, const unsigned char *key, int keylen,
-              const unsigned char *data, int datalen,
-              unsigned char *out, int *outlen) {
-    const EVP_MD *md = EVP_get_digestbyname(algo);
-    if (!md) return -1;
-    unsigned int len = 0;
-    unsigned char *result = HMAC(md, key, keylen, data, datalen, out, &len);
-    *outlen = (int)len;
-    return result ? 0 : -2;
-}
-
-/* ---- Cipher (Symmetric Encryption) ---- */
-
-void *chez_cipher_ctx_new(void) {
-    return EVP_CIPHER_CTX_new();
-}
-
-void chez_cipher_ctx_free(void *ctx) {
-    EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)ctx);
-}
-
-int chez_cipher_key_length(const char *algo) {
-    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
-    if (!c) return -1;
-    return EVP_CIPHER_key_length(c);
-}
-
-int chez_cipher_iv_length(const char *algo) {
-    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
-    if (!c) return -1;
-    return EVP_CIPHER_iv_length(c);
-}
-
-int chez_cipher_block_size(const char *algo) {
-    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
-    if (!c) return -1;
-    return EVP_CIPHER_block_size(c);
-}
-
-int chez_encrypt_init(void *ctx, const char *algo,
-                      const unsigned char *key, const unsigned char *iv) {
-    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
-    if (!c) return -1;
-    return EVP_EncryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2;
-}
-
-int chez_encrypt_update(void *ctx, const unsigned char *in, int inlen,
-                        unsigned char *out, int *outlen) {
-    return EVP_EncryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1;
-}
-
-int chez_encrypt_final(void *ctx, unsigned char *out, int *outlen) {
-    return EVP_EncryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1;
-}
-
-int chez_decrypt_init(void *ctx, const char *algo,
-                      const unsigned char *key, const unsigned char *iv) {
-    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
-    if (!c) return -1;
-    return EVP_DecryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2;
-}
-
-int chez_decrypt_update(void *ctx, const unsigned char *in, int inlen,
-                        unsigned char *out, int *outlen) {
-    return EVP_DecryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1;
-}
-
-int chez_decrypt_final(void *ctx, unsigned char *out, int *outlen) {
-    return EVP_DecryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1;
-}
-
-/* One-shot encrypt */
-int chez_encrypt(const char *algo, const unsigned char *key, const unsigned char *iv,
-                 const unsigned char *in, int inlen,
-                 unsigned char *out, int *outlen) {
-    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
-    if (!ctx) return -1;
-    int len1 = 0, len2 = 0;
-    int rc = -2;
-    if (chez_encrypt_init(ctx, algo, key, iv) == 0 &&
-        EVP_EncryptUpdate(ctx, out, &len1, in, inlen) == 1 &&
-        EVP_EncryptFinal_ex(ctx, out + len1, &len2) == 1) {
-        *outlen = len1 + len2;
-        rc = 0;
-    }
-    EVP_CIPHER_CTX_free(ctx);
-    return rc;
-}
-
-/* One-shot decrypt */
-int chez_decrypt(const char *algo, const unsigned char *key, const unsigned char *iv,
-                 const unsigned char *in, int inlen,
-                 unsigned char *out, int *outlen) {
-    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
-    if (!ctx) return -1;
-    int len1 = 0, len2 = 0;
-    int rc = -2;
-    if (chez_decrypt_init(ctx, algo, key, iv) == 0 &&
-        EVP_DecryptUpdate(ctx, out, &len1, in, inlen) == 1 &&
-        EVP_DecryptFinal_ex(ctx, out + len1, &len2) == 1) {
-        *outlen = len1 + len2;
-        rc = 0;
-    }
-    EVP_CIPHER_CTX_free(ctx);
-    return rc;
-}
-
-/* ---- Ed25519 Sign/Verify ---- */
-
-int chez_ed25519_keygen(unsigned char *privkey, int *privlen,
-                        unsigned char *pubkey, int *publen) {
-    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
-    if (!pctx) return -1;
-    EVP_PKEY *pkey = NULL;
-    int rc = -2;
-    if (EVP_PKEY_keygen_init(pctx) == 1 &&
-        EVP_PKEY_keygen(pctx, &pkey) == 1) {
-        size_t pl = 64, sl = 32;
-        if (EVP_PKEY_get_raw_private_key(pkey, privkey, &pl) == 1 &&
-            EVP_PKEY_get_raw_public_key(pkey, pubkey, &sl) == 1) {
-            *privlen = (int)pl;
-            *publen = (int)sl;
-            rc = 0;
-        }
-    }
-    if (pkey) EVP_PKEY_free(pkey);
-    EVP_PKEY_CTX_free(pctx);
-    return rc;
-}
-
-int chez_ed25519_sign(const unsigned char *privkey, int privlen,
-                      const unsigned char *msg, int msglen,
-                      unsigned char *sig, int *siglen) {
-    EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, privkey, privlen);
-    if (!pkey) return -1;
-    EVP_MD_CTX *mctx = EVP_MD_CTX_new();
-    int rc = -2;
-    size_t sl = 64;
-    if (EVP_DigestSignInit(mctx, NULL, NULL, NULL, pkey) == 1 &&
-        EVP_DigestSign(mctx, sig, &sl, msg, msglen) == 1) {
-        *siglen = (int)sl;
-        rc = 0;
-    }
-    EVP_MD_CTX_free(mctx);
-    EVP_PKEY_free(pkey);
-    return rc;
-}
-
-int chez_ed25519_verify(const unsigned char *pubkey, int publen,
-                        const unsigned char *msg, int msglen,
-                        const unsigned char *sig, int siglen) {
-    EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pubkey, publen);
-    if (!pkey) return -1;
-    EVP_MD_CTX *mctx = EVP_MD_CTX_new();
-    int rc = EVP_DigestVerifyInit(mctx, NULL, NULL, NULL, pkey) == 1 &&
-             EVP_DigestVerify(mctx, sig, siglen, msg, msglen) == 1 ? 1 : 0;
-    EVP_MD_CTX_free(mctx);
-    EVP_PKEY_free(pkey);
-    return rc;
-}
-
-/* ---- AEAD Encrypt/Decrypt (ChaCha20-Poly1305, AES-GCM, etc.) ---- */
-
-/*
- * chez_aead_encrypt — AEAD encrypt with authentication tag.
- *
- * algo:    cipher name (e.g. "chacha20-poly1305", "aes-256-gcm")
- * key:     encryption key
- * nonce:   nonce/IV
- * noncelen: nonce length (12 for chacha20-poly1305)
- * aad:     additional authenticated data (can be NULL)
- * aadlen:  AAD length (0 if no AAD)
- * in:      plaintext
- * inlen:   plaintext length
- * out:     ciphertext output (must be >= inlen bytes)
- * outlen:  receives ciphertext length
- * tag:     authentication tag output (must be >= taglen bytes)
- * taglen:  desired tag length (16 for chacha20-poly1305)
- *
- * Returns: 0 on success, negative on error.
- */
-int chez_aead_encrypt(const char *algo,
-                      const unsigned char *key,
-                      const unsigned char *nonce, int noncelen,
-                      const unsigned char *aad, int aadlen,
-                      const unsigned char *in, int inlen,
-                      unsigned char *out, int *outlen,
-                      unsigned char *tag, int taglen) {
-    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
-    if (!c) return -1;
-    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
-    if (!ctx) return -2;
-    int rc = -3;
-    int len1 = 0, len2 = 0;
-
-    if (EVP_EncryptInit_ex(ctx, c, NULL, NULL, NULL) != 1) goto done;
-    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, NULL) != 1) goto done;
-    if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) goto done;
-
-    if (aad && aadlen > 0) {
-        if (EVP_EncryptUpdate(ctx, NULL, &len1, aad, aadlen) != 1) goto done;
-    }
-    if (EVP_EncryptUpdate(ctx, out, &len1, in, inlen) != 1) goto done;
-    if (EVP_EncryptFinal_ex(ctx, out + len1, &len2) != 1) goto done;
-    *outlen = len1 + len2;
-
-    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, taglen, tag) != 1) goto done;
-    rc = 0;
-
-done:
-    EVP_CIPHER_CTX_free(ctx);
-    return rc;
-}
-
-/*
- * chez_aead_decrypt — AEAD decrypt and verify authentication tag.
- *
- * Returns: 0 on success (tag verified), -1 on auth failure, negative on error.
- */
-int chez_aead_decrypt(const char *algo,
-                      const unsigned char *key,
-                      const unsigned char *nonce, int noncelen,
-                      const unsigned char *aad, int aadlen,
-                      const unsigned char *in, int inlen,
-                      unsigned char *out, int *outlen,
-                      const unsigned char *tag, int taglen) {
-    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
-    if (!c) return -1;
-    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
-    if (!ctx) return -2;
-    int rc = -3;
-    int len1 = 0, len2 = 0;
-
-    if (EVP_DecryptInit_ex(ctx, c, NULL, NULL, NULL) != 1) goto done;
-    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, NULL) != 1) goto done;
-    if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) goto done;
-
-    if (aad && aadlen > 0) {
-        if (EVP_DecryptUpdate(ctx, NULL, &len1, aad, aadlen) != 1) goto done;
-    }
-    if (EVP_DecryptUpdate(ctx, out, &len1, in, inlen) != 1) goto done;
-
-    /* Set expected tag before finalize */
-    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, taglen,
-                            (void *)(unsigned char *)tag) != 1) goto done;
-
-    /* DecryptFinal returns 0 on tag mismatch */
-    if (EVP_DecryptFinal_ex(ctx, out + len1, &len2) != 1) {
-        rc = -1;  /* authentication failure */
-        goto done;
-    }
-    *outlen = len1 + len2;
-    rc = 0;
-
-done:
-    EVP_CIPHER_CTX_free(ctx);
-    return rc;
-}
-
-/* ---- Constant-time comparison ---- */
-
-int chez_constant_time_compare(const unsigned char *a,
-                                const unsigned char *b, int len) {
-    return CRYPTO_memcmp(a, b, len) == 0 ? 1 : 0;
-}
-
-/* ---- BN (Big Number) ---- */
-
-int chez_bn_bytes(const unsigned char *bin, int binlen) {
-    BIGNUM *bn = BN_bin2bn(bin, binlen, NULL);
-    if (!bn) return -1;
-    int n = BN_num_bytes(bn);
-    BN_free(bn);
-    return n;
-}
-
-/* ---- DH (Diffie-Hellman) ---- */
-/* Note: DH is deprecated in OpenSSL 3.x — included for API compat */
-
-/* ---- Scrypt KDF ---- */
-
-int chez_scrypt(const unsigned char *pass, int passlen,
-                const unsigned char *salt, int saltlen,
-                unsigned long long N, int r, int p,
-                unsigned char *out, int outlen) {
-    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, NULL);
-    if (!pctx) return -1;
-    int rc = -2;
-    size_t derived_len = outlen;
-    if (EVP_PKEY_derive_init(pctx) == 1 &&
-        EVP_PKEY_CTX_set1_pbe_pass(pctx, (const char *)pass, passlen) == 1 &&
-        EVP_PKEY_CTX_set1_scrypt_salt(pctx, salt, saltlen) == 1 &&
-        EVP_PKEY_CTX_set_scrypt_N(pctx, N) == 1 &&
-        EVP_PKEY_CTX_set_scrypt_r(pctx, r) == 1 &&
-        EVP_PKEY_CTX_set_scrypt_p(pctx, p) == 1 &&
-        EVP_PKEY_derive(pctx, out, &derived_len) == 1) {
-        rc = (int)derived_len;
-    }
-    EVP_PKEY_CTX_free(pctx);
-    return rc;
-}
diff --git a/jerboa_crypto_shim.c b/jerboa_crypto_shim.c
new file mode 100644
index 0000000..4db5b9a
--- /dev/null
+++ b/jerboa_crypto_shim.c
@@ -0,0 +1,386 @@
+/* jerboa_crypto_shim.c — OpenSSL libcrypto wrapper for Chez Scheme FFI */
+
+#include <openssl/evp.h>
+#include <openssl/hmac.h>
+#include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/rand.h>
+#include <openssl/err.h>
+#include <openssl/kdf.h>
+#include <openssl/crypto.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* ---- Error handling ---- */
+
+int jerboa_crypto_err_get(char *buf, int buflen) {
+    unsigned long e = ERR_get_error();
+    if (e == 0) {
+        buf[0] = 0;
+        return 0;
+    }
+    ERR_error_string_n(e, buf, buflen);
+    return 1;
+}
+
+/* ---- Random ---- */
+
+int jerboa_rand_bytes(unsigned char *buf, int n) {
+    return RAND_bytes(buf, n);
+}
+
+/* ---- Digest (Hash) ---- */
+
+void *jerboa_digest_ctx_new(void) {
+    return EVP_MD_CTX_new();
+}
+
+void jerboa_digest_ctx_free(void *ctx) {
+    EVP_MD_CTX_free((EVP_MD_CTX *)ctx);
+}
+
+int jerboa_digest_init(void *ctx, const char *algo) {
+    const EVP_MD *md = EVP_get_digestbyname(algo);
+    if (!md) return -1;
+    return EVP_DigestInit_ex((EVP_MD_CTX *)ctx, md, NULL) == 1 ? 0 : -2;
+}
+
+int jerboa_digest_update(void *ctx, const unsigned char *data, int len) {
+    return EVP_DigestUpdate((EVP_MD_CTX *)ctx, data, len) == 1 ? 0 : -1;
+}
+
+int jerboa_digest_final(void *ctx, unsigned char *out, int *outlen) {
+    unsigned int len = 0;
+    int rc = EVP_DigestFinal_ex((EVP_MD_CTX *)ctx, out, &len);
+    *outlen = (int)len;
+    return rc == 1 ? 0 : -1;
+}
+
+int jerboa_digest_size(const char *algo) {
+    const EVP_MD *md = EVP_get_digestbyname(algo);
+    if (!md) return -1;
+    return EVP_MD_size(md);
+}
+
+/* One-shot digest */
+int jerboa_digest(const char *algo, const unsigned char *data, int datalen,
+                unsigned char *out, int *outlen) {
+    const EVP_MD *md = EVP_get_digestbyname(algo);
+    if (!md) return -1;
+    EVP_MD_CTX *ctx = EVP_MD_CTX_new();
+    if (!ctx) return -2;
+    unsigned int len = 0;
+    int rc = -3;
+    if (EVP_DigestInit_ex(ctx, md, NULL) == 1 &&
+        EVP_DigestUpdate(ctx, data, datalen) == 1 &&
+        EVP_DigestFinal_ex(ctx, out, &len) == 1) {
+        rc = 0;
+    }
+    *outlen = (int)len;
+    EVP_MD_CTX_free(ctx);
+    return rc;
+}
+
+/* ---- HMAC ---- */
+
+int jerboa_hmac(const char *algo, const unsigned char *key, int keylen,
+              const unsigned char *data, int datalen,
+              unsigned char *out, int *outlen) {
+    const EVP_MD *md = EVP_get_digestbyname(algo);
+    if (!md) return -1;
+    unsigned int len = 0;
+    unsigned char *result = HMAC(md, key, keylen, data, datalen, out, &len);
+    *outlen = (int)len;
+    return result ? 0 : -2;
+}
+
+/* ---- Cipher (Symmetric Encryption) ---- */
+
+void *jerboa_cipher_ctx_new(void) {
+    return EVP_CIPHER_CTX_new();
+}
+
+void jerboa_cipher_ctx_free(void *ctx) {
+    EVP_CIPHER_CTX_free((EVP_CIPHER_CTX *)ctx);
+}
+
+int jerboa_cipher_key_length(const char *algo) {
+    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
+    if (!c) return -1;
+    return EVP_CIPHER_key_length(c);
+}
+
+int jerboa_cipher_iv_length(const char *algo) {
+    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
+    if (!c) return -1;
+    return EVP_CIPHER_iv_length(c);
+}
+
+int jerboa_cipher_block_size(const char *algo) {
+    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
+    if (!c) return -1;
+    return EVP_CIPHER_block_size(c);
+}
+
+int jerboa_encrypt_init(void *ctx, const char *algo,
+                      const unsigned char *key, const unsigned char *iv) {
+    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
+    if (!c) return -1;
+    return EVP_EncryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2;
+}
+
+int jerboa_encrypt_update(void *ctx, const unsigned char *in, int inlen,
+                        unsigned char *out, int *outlen) {
+    return EVP_EncryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1;
+}
+
+int jerboa_encrypt_final(void *ctx, unsigned char *out, int *outlen) {
+    return EVP_EncryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1;
+}
+
+int jerboa_decrypt_init(void *ctx, const char *algo,
+                      const unsigned char *key, const unsigned char *iv) {
+    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
+    if (!c) return -1;
+    return EVP_DecryptInit_ex((EVP_CIPHER_CTX *)ctx, c, NULL, key, iv) == 1 ? 0 : -2;
+}
+
+int jerboa_decrypt_update(void *ctx, const unsigned char *in, int inlen,
+                        unsigned char *out, int *outlen) {
+    return EVP_DecryptUpdate((EVP_CIPHER_CTX *)ctx, out, outlen, in, inlen) == 1 ? 0 : -1;
+}
+
+int jerboa_decrypt_final(void *ctx, unsigned char *out, int *outlen) {
+    return EVP_DecryptFinal_ex((EVP_CIPHER_CTX *)ctx, out, outlen) == 1 ? 0 : -1;
+}
+
+/* One-shot encrypt */
+int jerboa_encrypt(const char *algo, const unsigned char *key, const unsigned char *iv,
+                 const unsigned char *in, int inlen,
+                 unsigned char *out, int *outlen) {
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) return -1;
+    int len1 = 0, len2 = 0;
+    int rc = -2;
+    if (jerboa_encrypt_init(ctx, algo, key, iv) == 0 &&
+        EVP_EncryptUpdate(ctx, out, &len1, in, inlen) == 1 &&
+        EVP_EncryptFinal_ex(ctx, out + len1, &len2) == 1) {
+        *outlen = len1 + len2;
+        rc = 0;
+    }
+    EVP_CIPHER_CTX_free(ctx);
+    return rc;
+}
+
+/* One-shot decrypt */
+int jerboa_decrypt(const char *algo, const unsigned char *key, const unsigned char *iv,
+                 const unsigned char *in, int inlen,
+                 unsigned char *out, int *outlen) {
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) return -1;
+    int len1 = 0, len2 = 0;
+    int rc = -2;
+    if (jerboa_decrypt_init(ctx, algo, key, iv) == 0 &&
+        EVP_DecryptUpdate(ctx, out, &len1, in, inlen) == 1 &&
+        EVP_DecryptFinal_ex(ctx, out + len1, &len2) == 1) {
+        *outlen = len1 + len2;
+        rc = 0;
+    }
+    EVP_CIPHER_CTX_free(ctx);
+    return rc;
+}
+
+/* ---- Ed25519 Sign/Verify ---- */
+
+int jerboa_ed25519_keygen(unsigned char *privkey, int *privlen,
+                        unsigned char *pubkey, int *publen) {
+    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL);
+    if (!pctx) return -1;
+    EVP_PKEY *pkey = NULL;
+    int rc = -2;
+    if (EVP_PKEY_keygen_init(pctx) == 1 &&
+        EVP_PKEY_keygen(pctx, &pkey) == 1) {
+        size_t pl = 64, sl = 32;
+        if (EVP_PKEY_get_raw_private_key(pkey, privkey, &pl) == 1 &&
+            EVP_PKEY_get_raw_public_key(pkey, pubkey, &sl) == 1) {
+            *privlen = (int)pl;
+            *publen = (int)sl;
+            rc = 0;
+        }
+    }
+    if (pkey) EVP_PKEY_free(pkey);
+    EVP_PKEY_CTX_free(pctx);
+    return rc;
+}
+
+int jerboa_ed25519_sign(const unsigned char *privkey, int privlen,
+                      const unsigned char *msg, int msglen,
+                      unsigned char *sig, int *siglen) {
+    EVP_PKEY *pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, privkey, privlen);
+    if (!pkey) return -1;
+    EVP_MD_CTX *mctx = EVP_MD_CTX_new();
+    int rc = -2;
+    size_t sl = 64;
+    if (EVP_DigestSignInit(mctx, NULL, NULL, NULL, pkey) == 1 &&
+        EVP_DigestSign(mctx, sig, &sl, msg, msglen) == 1) {
+        *siglen = (int)sl;
+        rc = 0;
+    }
+    EVP_MD_CTX_free(mctx);
+    EVP_PKEY_free(pkey);
+    return rc;
+}
+
+int jerboa_ed25519_verify(const unsigned char *pubkey, int publen,
+                        const unsigned char *msg, int msglen,
+                        const unsigned char *sig, int siglen) {
+    EVP_PKEY *pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, NULL, pubkey, publen);
+    if (!pkey) return -1;
+    EVP_MD_CTX *mctx = EVP_MD_CTX_new();
+    int rc = EVP_DigestVerifyInit(mctx, NULL, NULL, NULL, pkey) == 1 &&
+             EVP_DigestVerify(mctx, sig, siglen, msg, msglen) == 1 ? 1 : 0;
+    EVP_MD_CTX_free(mctx);
+    EVP_PKEY_free(pkey);
+    return rc;
+}
+
+/* ---- AEAD Encrypt/Decrypt (ChaCha20-Poly1305, AES-GCM, etc.) ---- */
+
+/*
+ * jerboa_aead_encrypt — AEAD encrypt with authentication tag.
+ *
+ * algo:    cipher name (e.g. "chacha20-poly1305", "aes-256-gcm")
+ * key:     encryption key
+ * nonce:   nonce/IV
+ * noncelen: nonce length (12 for chacha20-poly1305)
+ * aad:     additional authenticated data (can be NULL)
+ * aadlen:  AAD length (0 if no AAD)
+ * in:      plaintext
+ * inlen:   plaintext length
+ * out:     ciphertext output (must be >= inlen bytes)
+ * outlen:  receives ciphertext length
+ * tag:     authentication tag output (must be >= taglen bytes)
+ * taglen:  desired tag length (16 for chacha20-poly1305)
+ *
+ * Returns: 0 on success, negative on error.
+ */
+int jerboa_aead_encrypt(const char *algo,
+                      const unsigned char *key,
+                      const unsigned char *nonce, int noncelen,
+                      const unsigned char *aad, int aadlen,
+                      const unsigned char *in, int inlen,
+                      unsigned char *out, int *outlen,
+                      unsigned char *tag, int taglen) {
+    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
+    if (!c) return -1;
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) return -2;
+    int rc = -3;
+    int len1 = 0, len2 = 0;
+
+    if (EVP_EncryptInit_ex(ctx, c, NULL, NULL, NULL) != 1) goto done;
+    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, NULL) != 1) goto done;
+    if (EVP_EncryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) goto done;
+
+    if (aad && aadlen > 0) {
+        if (EVP_EncryptUpdate(ctx, NULL, &len1, aad, aadlen) != 1) goto done;
+    }
+    if (EVP_EncryptUpdate(ctx, out, &len1, in, inlen) != 1) goto done;
+    if (EVP_EncryptFinal_ex(ctx, out + len1, &len2) != 1) goto done;
+    *outlen = len1 + len2;
+
+    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, taglen, tag) != 1) goto done;
+    rc = 0;
+
+done:
+    EVP_CIPHER_CTX_free(ctx);
+    return rc;
+}
+
+/*
+ * jerboa_aead_decrypt — AEAD decrypt and verify authentication tag.
+ *
+ * Returns: 0 on success (tag verified), -1 on auth failure, negative on error.
+ */
+int jerboa_aead_decrypt(const char *algo,
+                      const unsigned char *key,
+                      const unsigned char *nonce, int noncelen,
+                      const unsigned char *aad, int aadlen,
+                      const unsigned char *in, int inlen,
+                      unsigned char *out, int *outlen,
+                      const unsigned char *tag, int taglen) {
+    const EVP_CIPHER *c = EVP_get_cipherbyname(algo);
+    if (!c) return -1;
+    EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+    if (!ctx) return -2;
+    int rc = -3;
+    int len1 = 0, len2 = 0;
+
+    if (EVP_DecryptInit_ex(ctx, c, NULL, NULL, NULL) != 1) goto done;
+    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, noncelen, NULL) != 1) goto done;
+    if (EVP_DecryptInit_ex(ctx, NULL, NULL, key, nonce) != 1) goto done;
+
+    if (aad && aadlen > 0) {
+        if (EVP_DecryptUpdate(ctx, NULL, &len1, aad, aadlen) != 1) goto done;
+    }
+    if (EVP_DecryptUpdate(ctx, out, &len1, in, inlen) != 1) goto done;
+
+    /* Set expected tag before finalize */
+    if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, taglen,
+                            (void *)(unsigned char *)tag) != 1) goto done;
+
+    /* DecryptFinal returns 0 on tag mismatch */
+    if (EVP_DecryptFinal_ex(ctx, out + len1, &len2) != 1) {
+        rc = -1;  /* authentication failure */
+        goto done;
+    }
+    *outlen = len1 + len2;
+    rc = 0;
+
+done:
+    EVP_CIPHER_CTX_free(ctx);
+    return rc;
+}
+
+/* ---- Constant-time comparison ---- */
+
+int jerboa_constant_time_compare(const unsigned char *a,
+                                const unsigned char *b, int len) {
+    return CRYPTO_memcmp(a, b, len) == 0 ? 1 : 0;
+}
+
+/* ---- BN (Big Number) ---- */
+
+int jerboa_bn_bytes(const unsigned char *bin, int binlen) {
+    BIGNUM *bn = BN_bin2bn(bin, binlen, NULL);
+    if (!bn) return -1;
+    int n = BN_num_bytes(bn);
+    BN_free(bn);
+    return n;
+}
+
+/* ---- DH (Diffie-Hellman) ---- */
+/* Note: DH is deprecated in OpenSSL 3.x — included for API compat */
+
+/* ---- Scrypt KDF ---- */
+
+int jerboa_scrypt(const unsigned char *pass, int passlen,
+                const unsigned char *salt, int saltlen,
+                unsigned long long N, int r, int p,
+                unsigned char *out, int outlen) {
+    EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_SCRYPT, NULL);
+    if (!pctx) return -1;
+    int rc = -2;
+    size_t derived_len = outlen;
+    if (EVP_PKEY_derive_init(pctx) == 1 &&
+        EVP_PKEY_CTX_set1_pbe_pass(pctx, (const char *)pass, passlen) == 1 &&
+        EVP_PKEY_CTX_set1_scrypt_salt(pctx, salt, saltlen) == 1 &&
+        EVP_PKEY_CTX_set_scrypt_N(pctx, N) == 1 &&
+        EVP_PKEY_CTX_set_scrypt_r(pctx, r) == 1 &&
+        EVP_PKEY_CTX_set_scrypt_p(pctx, p) == 1 &&
+        EVP_PKEY_derive(pctx, out, &derived_len) == 1) {
+        rc = (int)derived_len;
+    }
+    EVP_PKEY_CTX_free(pctx);
+    return rc;
+}
diff --git a/src/chez-crypto.sls b/src/chez-crypto.sls
deleted file mode 100644
index a11b84f..0000000
--- a/src/chez-crypto.sls
+++ /dev/null
@@ -1,358 +0,0 @@
-#!chezscheme
-;;; chez-crypto — OpenSSL libcrypto for Chez Scheme
-
-(library (chez-crypto)
-  (export
-    ;; Random
-    random-bytes random-bytes!
-    ;; Digest (Hash)
-    digest digest-size
-    md5 sha1 sha224 sha256 sha384 sha512
-    make-digest-ctx free-digest-ctx
-    digest-init! digest-update! digest-final!
-    ;; HMAC
-    hmac hmac-md5 hmac-sha1 hmac-sha256 hmac-sha384 hmac-sha512
-    ;; Cipher (Symmetric)
-    encrypt decrypt
-    cipher-key-length cipher-iv-length cipher-block-size
-    make-cipher-ctx free-cipher-ctx
-    encrypt-init! encrypt-update! encrypt-final!
-    decrypt-init! decrypt-update! decrypt-final!
-    ;; Public Key — Ed25519
-    ed25519-keygen ed25519-sign ed25519-verify
-    ;; AEAD (Authenticated Encryption)
-    aead-encrypt aead-decrypt
-    ;; Constant-time comparison
-    constant-time-compare?
-    ;; KDF
-    scrypt
-    ;; Error
-    crypto-error-string)
-
-  (import (chezscheme))
-
-  ;; Load shared objects.
-  ;; In static/binary builds where symbols are pre-registered via Sforeign_symbol,
-  ;; load-shared-object may fail (no dlopen) — guard to allow graceful fallback.
-  (define _l1
-    (guard (e [#t (void)])
-      (load-shared-object "libcrypto.so")))
-  (define _l2
-    (guard (e [#t (void)])
-      (load-shared-object "chez_crypto_shim.so")))
-
-  ;; ---- FFI bindings ----
-  (define c-err-get      (foreign-procedure "chez_crypto_err_get" (u8* int) int))
-  (define c-rand-bytes   (foreign-procedure "chez_rand_bytes" (u8* int) int))
-  (define c-digest       (foreign-procedure "chez_digest" (string u8* int u8* u8*) int))
-  (define c-digest-size  (foreign-procedure "chez_digest_size" (string) int))
-  (define c-digest-ctx-new  (foreign-procedure "chez_digest_ctx_new" () void*))
-  (define c-digest-ctx-free (foreign-procedure "chez_digest_ctx_free" (void*) void))
-  (define c-digest-init  (foreign-procedure "chez_digest_init" (void* string) int))
-  (define c-digest-update (foreign-procedure "chez_digest_update" (void* u8* int) int))
-  (define c-digest-final (foreign-procedure "chez_digest_final" (void* u8* u8*) int))
-  (define c-hmac         (foreign-procedure "chez_hmac" (string u8* int u8* int u8* u8*) int))
-  (define c-cipher-ctx-new  (foreign-procedure "chez_cipher_ctx_new" () void*))
-  (define c-cipher-ctx-free (foreign-procedure "chez_cipher_ctx_free" (void*) void))
-  (define c-cipher-key-length (foreign-procedure "chez_cipher_key_length" (string) int))
-  (define c-cipher-iv-length  (foreign-procedure "chez_cipher_iv_length" (string) int))
-  (define c-cipher-block-size (foreign-procedure "chez_cipher_block_size" (string) int))
-  (define c-encrypt-init  (foreign-procedure "chez_encrypt_init" (void* string u8* u8*) int))
-  (define c-encrypt-update (foreign-procedure "chez_encrypt_update" (void* u8* int u8* u8*) int))
-  (define c-encrypt-final  (foreign-procedure "chez_encrypt_final" (void* u8* u8*) int))
-  (define c-decrypt-init  (foreign-procedure "chez_decrypt_init" (void* string u8* u8*) int))
-  (define c-decrypt-update (foreign-procedure "chez_decrypt_update" (void* u8* int u8* u8*) int))
-  (define c-decrypt-final  (foreign-procedure "chez_decrypt_final" (void* u8* u8*) int))
-  (define c-encrypt (foreign-procedure "chez_encrypt" (string u8* u8* u8* int u8* u8*) int))
-  (define c-decrypt (foreign-procedure "chez_decrypt" (string u8* u8* u8* int u8* u8*) int))
-  (define c-ed25519-keygen (foreign-procedure "chez_ed25519_keygen" (u8* u8* u8* u8*) int))
-  (define c-ed25519-sign   (foreign-procedure "chez_ed25519_sign" (u8* int u8* int u8* u8*) int))
-  (define c-ed25519-verify (foreign-procedure "chez_ed25519_verify" (u8* int u8* int u8* int) int))
-  (define c-scrypt (foreign-procedure "chez_scrypt" (u8* int u8* int unsigned-64 int int u8* int) int))
-  (define c-aead-encrypt (foreign-procedure "chez_aead_encrypt"
-    (string u8* u8* int u8* int u8* int u8* u8* u8* int) int))
-  (define c-aead-decrypt (foreign-procedure "chez_aead_decrypt"
-    (string u8* u8* int u8* int u8* int u8* u8* u8* int) int))
-  (define c-constant-time-compare (foreign-procedure "chez_constant_time_compare"
-    (u8* u8* int) int))
-
-  ;; ---- Helpers ----
-  (define (check-rc who rc)
-    (when (< rc 0)
-      (error who (string-append "crypto error (rc=" (number->string rc) ")"))))
-
-  (define (as-bytes x)
-    (if (string? x) (string->utf8 x) x))
-
-  (define (int-ref bv)
-    (bytevector-s32-native-ref bv 0))
-
-  (define (make-int-buf)
-    (make-bytevector 4 0))
-
-  ;; ---- Error ----
-  (define (crypto-error-string)
-    (let ([buf (make-bytevector 256 0)])
-      (if (= (c-err-get buf 256) 1)
-        (let loop ([i 0])
-          (if (or (= i 256) (= (bytevector-u8-ref buf i) 0))
-            (utf8->string (let ([r (make-bytevector i)])
-                            (bytevector-copy! buf 0 r 0 i) r))
-            (loop (+ i 1))))
-        #f)))
-
-  ;; ---- Random ----
-  (define (random-bytes n)
-    (let ([bv (make-bytevector n)])
-      (random-bytes! bv)
-      bv))
-
-  (define (random-bytes! bv)
-    (let ([rc (c-rand-bytes bv (bytevector-length bv))])
-      (unless (= rc 1) (error 'random-bytes! "RAND_bytes failed"))))
-
-  ;; ---- Digest ----
-  (define (digest-size algo)
-    (let ([n (c-digest-size algo)])
-      (check-rc 'digest-size n)
-      n))
-
-  (define (digest algo data)
-    (let* ([data (as-bytes data)]
-           [out (make-bytevector 64 0)]
-           [lenp (make-int-buf)]
-           [rc (c-digest algo data (bytevector-length data) out lenp)])
-      (check-rc 'digest rc)
-      (let ([len (int-ref lenp)]
-            [result (make-bytevector (int-ref lenp))])
-        (bytevector-copy! out 0 result 0 len)
-        result)))
-
-  (define (md5 data)    (digest "md5" data))
-  (define (sha1 data)   (digest "sha1" data))
-  (define (sha224 data) (digest "sha224" data))
-  (define (sha256 data) (digest "sha256" data))
-  (define (sha384 data) (digest "sha384" data))
-  (define (sha512 data) (digest "sha512" data))
-
-  ;; Streaming digest
-  (define (make-digest-ctx) (c-digest-ctx-new))
-  (define (free-digest-ctx ctx) (c-digest-ctx-free ctx))
-
-  (define (digest-init! ctx algo)
-    (check-rc 'digest-init! (c-digest-init ctx algo)))
-
-  (define (digest-update! ctx data)
-    (let ([data (as-bytes data)])
-      (check-rc 'digest-update! (c-digest-update ctx data (bytevector-length data)))))
-
-  (define (digest-final! ctx)
-    (let ([out (make-bytevector 64 0)]
-          [lenp (make-int-buf)])
-      (check-rc 'digest-final! (c-digest-final ctx out lenp))
-      (let ([len (int-ref lenp)]