AtlatestRepositorysigil-crypto
1/*
2 * Sigil Crypto Library Implementation
3 *
4 * This file implements cryptographic operations using mbedTLS:
5 * - SHA-256 hashing
6 * - HMAC-SHA256 message authentication
7 * - Base64 encoding/decoding
8 * - Cryptographically secure random number generation (CTR-DRBG)
9 *
10 * Ed25519 and BLAKE2b-512 live in native/ed25519.c (vendored Monocypher)
11 * and are registered into the same module on every target.
12 *
13 * On wasm (wasm32-wasi, and Emscripten) Mbed TLS is not built: it needs a
14 * platform entropy source, sockets and a clock that the wasm build does not
15 * provide yet (t-8c222f). There the module carries only the Monocypher
16 * natives; every Mbed TLS-backed name is registered as a stub that raises
17 * "not available on wasm" when called, so a program that uses one fails
18 * with that message rather than an unbound variable.
19 */
21#include <sigil/sigil.h>
23#include "ed25519.h"
25#if defined(__EMSCRIPTEN__) || defined(__wasi__)
27/* Keep in step with the registrations in the native branch below. The wasm
28 * harness (test/wasm/run-wasm-test.mjs) calls every (sigil crypto) export
29 * that is not in its working set and fails if one is missing here. */
30static const char *const mbedtls_only_natives[] = {
31 "sha1", "sha256", "sha256-bytes", "ripemd160",
32 "hmac-sha256", "hmac-sha256-bytes", "hmac-sha512-bytes", "hmac-sha1",
33 "pbkdf2-sha1", "pbkdf2-sha256", "pbkdf2-sha512",
34 "base64-encode", "base64-decode", "random-bytes",
35 "mpi-add", "mpi-sub", "mpi-mul", "mpi-div", "mpi-mod", "mpi-mod-add",
36 "mpi-inv-mod", "mpi-cmp", "mpi-is-zero?", "mpi-shift-l", "mpi-shift-r",
37 "ecdsa-p256-generate-keypair", "ecdsa-p256-sign", "ecdsa-p256-verify",
38 "ecdh-p256-shared-secret", "aes-128-gcm-encrypt", "aes-128-gcm-decrypt",
39 NULL
40};
42static Value native_unavailable_on_wasm(SigilVM *vm, int argc, Value *args)
44 (void)argc;
45 (void)args;
46 sigil__vm_error(vm, SIGIL_ERR_RUNTIME,
47 "(sigil crypto): not available on wasm; this procedure "
48 "needs Mbed TLS, which the wasm build does not include. "
49 "On wasm only ed25519-verify, ed25519-public-key, "
50 "ed25519-sign, blake2b-512 and (sigil crypto minisign) "
51 "are available");
52 return SIGIL_UNDEFINED;
55void sigil__init_sigil_crypto_module(SigilVM *vm)
57 SigilModule *module = sigil_begin_module(vm, "(sigil crypto)");
58 if (!module) return;
60 for (const char *const *name = mbedtls_only_natives; *name; name++) {
61 sigil_module_register_native(vm, *name, native_unavailable_on_wasm,
62 SIGIL_ARITY_VARIADIC,
63 "Not available on wasm (needs Mbed TLS)");
64 sigil_module_export(vm, *name);
65 }
67 sigil_crypto_register_ed25519(vm);
69 sigil_end_module(vm);
72#else /* Native build */
73#include <stdio.h>
74#include <stdlib.h>
75#include <string.h>
77#include "mbedtls/sha1.h"
78#include "mbedtls/sha256.h"
79#include "mbedtls/ripemd160.h"
80#include "mbedtls/md.h"
81#include "mbedtls/base64.h"
82#include "mbedtls/entropy.h"
83#include "mbedtls/ctr_drbg.h"
84#include "mbedtls/pkcs5.h"
85#include "mbedtls/ecp.h"
86#include "mbedtls/ecdsa.h"
87#include "mbedtls/ecdh.h"
88#include "mbedtls/bignum.h"
89#include "mbedtls/gcm.h"
91/* Global RNG context (initialized on first use) */
92static mbedtls_entropy_context entropy_ctx;
93static mbedtls_ctr_drbg_context ctr_drbg_ctx;
94static int rng_initialized = 0;
96/*
97 * Initialize the random number generator on first use.
98 * Returns 0 on success, non-zero on failure.
99 */
100static int ensure_rng_initialized(void)
102 if (rng_initialized) return 0;
104 mbedtls_entropy_init(&entropy_ctx);
105 mbedtls_ctr_drbg_init(&ctr_drbg_ctx);
107 /* Seed the DRBG with entropy from the OS */
108 int ret = mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func,
109 &entropy_ctx, NULL, 0);
110 if (ret != 0) {
111 mbedtls_ctr_drbg_free(&ctr_drbg_ctx);
112 mbedtls_entropy_free(&entropy_ctx);
113 return ret;
114 }
116 rng_initialized = 1;
117 return 0;
120/*
121 * Helper: Convert bytes to hex string
122 */
123static void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex)
125 static const char hex_chars[] = "0123456789abcdef";
126 for (size_t i = 0; i < len; i++) {
127 hex[i * 2] = hex_chars[(bytes[i] >> 4) & 0x0f];
128 hex[i * 2 + 1] = hex_chars[bytes[i] & 0x0f];
129 }
130 hex[len * 2] = '\0';
133/*
134 * sha256 data -> hex-string
135 * Compute SHA-256 hash of data (string or bytevector).
136 * Returns 64-character hex-encoded hash.
137 */
138static Value native_sha256(SigilVM *vm, int argc, Value *args)
140 (void)argc;
142 const unsigned char *data;
143 size_t len;
145 if (sigil_is_string(args[0])) {
146 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
147 data = (const unsigned char *)s->data;
148 len = s->byte_length;
149 } else if (sigil_is_bytevector(args[0])) {
150 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
151 data = bv->data;
152 len = bv->length;
153 } else {
154 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sha256: expected string or bytevector");
155 return SIGIL_UNDEFINED;
156 }
158 unsigned char hash[32]; /* SHA-256 produces 32 bytes */
160 /* Use mbedTLS SHA-256 */
161 mbedtls_sha256_context ctx;
162 mbedtls_sha256_init(&ctx);
163 mbedtls_sha256_starts(&ctx, 0); /* 0 = SHA-256, 1 = SHA-224 */
164 mbedtls_sha256_update(&ctx, data, len);
165 mbedtls_sha256_finish(&ctx, hash);
166 mbedtls_sha256_free(&ctx);
168 /* Convert to hex string */
169 char hex[65];
170 bytes_to_hex(hash, 32, hex);
172 return sigil_make_string(vm, hex, 64);
175/*
176 * sha256-bytes data -> bytevector
177 * Compute SHA-256 hash of data (string or bytevector).
178 * Returns 32-byte hash as bytevector.
179 */
180static Value native_sha256_bytes(SigilVM *vm, int argc, Value *args)
182 (void)argc;
184 const unsigned char *data;
185 size_t len;
187 if (sigil_is_string(args[0])) {
188 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
189 data = (const unsigned char *)s->data;
190 len = s->byte_length;
191 } else if (sigil_is_bytevector(args[0])) {
192 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
193 data = bv->data;
194 len = bv->length;
195 } else {
196 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sha256-bytes: expected string or bytevector");
197 return SIGIL_UNDEFINED;
198 }
200 unsigned char hash[32]; /* SHA-256 produces 32 bytes */
202 /* Use mbedTLS SHA-256 */
203 mbedtls_sha256_context ctx;
204 mbedtls_sha256_init(&ctx);
205 mbedtls_sha256_starts(&ctx, 0); /* 0 = SHA-256, 1 = SHA-224 */
206 mbedtls_sha256_update(&ctx, data, len);
207 mbedtls_sha256_finish(&ctx, hash);
208 mbedtls_sha256_free(&ctx);
210 /* Return as bytevector */
211 Value result = sigil_make_bytevector(vm, 32);
212 if (sigil_is_bytevector(result)) {
213 memcpy(sigil_bytevector_data(result), hash, 32);
214 }
215 return result;
218/*
219 * sha1 data -> bytevector
220 * Compute SHA-1 hash of data (string or bytevector).
221 * Returns 20-byte hash as bytevector (for WebSocket handshake compatibility).
222 * Note: SHA-1 is cryptographically weak; use SHA-256 for new applications.
223 */
224static Value native_sha1(SigilVM *vm, int argc, Value *args)
226 (void)argc;
228 const unsigned char *data;
229 size_t len;
231 if (sigil_is_string(args[0])) {
232 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
233 data = (const unsigned char *)s->data;
234 len = s->byte_length;
235 } else if (sigil_is_bytevector(args[0])) {
236 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
237 data = bv->data;
238 len = bv->length;
239 } else {
240 sigil__vm_error(vm, SIGIL_ERR_TYPE, "sha1: expected string or bytevector");
241 return SIGIL_UNDEFINED;
242 }
244 unsigned char hash[20]; /* SHA-1 produces 20 bytes */
246 /* Use mbedTLS SHA-1 */
247 mbedtls_sha1_context ctx;
248 mbedtls_sha1_init(&ctx);
249 mbedtls_sha1_starts(&ctx);
250 mbedtls_sha1_update(&ctx, data, len);
251 mbedtls_sha1_finish(&ctx, hash);
252 mbedtls_sha1_free(&ctx);
254 /* Return as bytevector for use with base64-encode */
255 Value result = sigil_make_bytevector(vm, 20);
256 if (sigil_is_bytevector(result)) {
257 memcpy(sigil_bytevector_data(result), hash, 20);
258 }
259 return result;
262/*
263 * ripemd160 data -> bytevector
264 * Compute RIPEMD-160 hash of data (string or bytevector).
265 * Returns 20-byte hash as bytevector.
266 */
267static Value native_ripemd160(SigilVM *vm, int argc, Value *args)
269 (void)argc;
271 const unsigned char *data;
272 size_t len;
274 if (sigil_is_string(args[0])) {
275 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
276 data = (const unsigned char *)s->data;
277 len = s->byte_length;
278 } else if (sigil_is_bytevector(args[0])) {
279 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
280 data = bv->data;
281 len = bv->length;
282 } else {
283 sigil__vm_error(vm, SIGIL_ERR_TYPE, "ripemd160: expected string or bytevector");
284 return SIGIL_UNDEFINED;
285 }
287 unsigned char hash[20];
289 mbedtls_ripemd160_context ctx;
290 mbedtls_ripemd160_init(&ctx);
291 mbedtls_ripemd160_starts(&ctx);
292 mbedtls_ripemd160_update(&ctx, data, len);
293 mbedtls_ripemd160_finish(&ctx, hash);
294 mbedtls_ripemd160_free(&ctx);
296 Value result = sigil_make_bytevector(vm, 20);
297 if (sigil_is_bytevector(result)) {
298 memcpy(sigil_bytevector_data(result), hash, 20);
299 }
300 return result;
303/*
304 * hmac-sha256 key data -> hex-string
305 * Compute HMAC-SHA256 of data using the given key.
306 * Both key and data can be strings or bytevectors.
307 * Returns 64-character hex-encoded MAC.
308 */
309static Value native_hmac_sha256(SigilVM *vm, int argc, Value *args)
311 (void)argc;
313 const unsigned char *key_data;
314 size_t key_len;
315 const unsigned char *msg_data;
316 size_t msg_len;
318 /* Get key */
319 if (sigil_is_string(args[0])) {
320 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
321 key_data = (const unsigned char *)s->data;
322 key_len = s->byte_length;
323 } else if (sigil_is_bytevector(args[0])) {
324 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
325 key_data = bv->data;
326 key_len = bv->length;
327 } else {
328 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256: expected string or bytevector for key");
329 return SIGIL_UNDEFINED;
330 }
332 /* Get message */
333 if (sigil_is_string(args[1])) {
334 SigilString *s = (SigilString *)sigil_as_ptr(args[1]);
335 msg_data = (const unsigned char *)s->data;
336 msg_len = s->byte_length;
337 } else if (sigil_is_bytevector(args[1])) {
338 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);
339 msg_data = bv->data;
340 msg_len = bv->length;
341 } else {
342 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256: expected string or bytevector for data");
343 return SIGIL_UNDEFINED;
344 }
346 unsigned char hmac[32]; /* HMAC-SHA256 produces 32 bytes */
348 /* Use mbedTLS HMAC */
349 const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
350 int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);
351 if (ret != 0) {
352 return SIGIL_FALSE;
353 }
355 /* Convert to hex string */
356 char hex[65];
357 bytes_to_hex(hmac, 32, hex);
359 return sigil_make_string(vm, hex, 64);
362/*
363 * hmac-sha256-bytes key data -> bytevector
364 * Like hmac-sha256 but returns the 32-byte MAC as a bytevector instead
365 * of a hex string. Required for SCRAM-SHA-256 where intermediate values
366 * are bytewise XORed and concatenated.
367 */
368static Value native_hmac_sha256_bytes(SigilVM *vm, int argc, Value *args)
370 (void)argc;
372 const unsigned char *key_data;
373 size_t key_len;
374 const unsigned char *msg_data;
375 size_t msg_len;
377 if (sigil_is_string(args[0])) {
378 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
379 key_data = (const unsigned char *)s->data;
380 key_len = s->byte_length;
381 } else if (sigil_is_bytevector(args[0])) {
382 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
383 key_data = bv->data;
384 key_len = bv->length;
385 } else {
386 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256-bytes: expected string or bytevector for key");
387 return SIGIL_UNDEFINED;
388 }
390 if (sigil_is_string(args[1])) {
391 SigilString *s = (SigilString *)sigil_as_ptr(args[1]);
392 msg_data = (const unsigned char *)s->data;
393 msg_len = s->byte_length;
394 } else if (sigil_is_bytevector(args[1])) {
395 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);
396 msg_data = bv->data;
397 msg_len = bv->length;
398 } else {
399 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256-bytes: expected string or bytevector for data");
400 return SIGIL_UNDEFINED;
401 }
403 unsigned char hmac[32];
405 const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
406 int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);
407 if (ret != 0) {
408 return SIGIL_FALSE;
409 }
411 Value result = sigil_make_bytevector(vm, 32);
412 if (sigil_is_bytevector(result)) {
413 memcpy(sigil_bytevector_data(result), hmac, 32);
414 }
415 return result;
418/*
419 * hmac-sha512-bytes key data -> bytevector
420 * Like hmac-sha256-bytes but using SHA-512.
421 */
422static Value native_hmac_sha512_bytes(SigilVM *vm, int argc, Value *args)
424 (void)argc;
426 const unsigned char *key_data;
427 size_t key_len;
428 const unsigned char *msg_data;
429 size_t msg_len;
431 if (sigil_is_string(args[0])) {
432 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
433 key_data = (const unsigned char *)s->data;
434 key_len = s->byte_length;
435 } else if (sigil_is_bytevector(args[0])) {
436 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
437 key_data = bv->data;
438 key_len = bv->length;
439 } else {
440 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha512-bytes: expected string or bytevector for key");
441 return SIGIL_UNDEFINED;
442 }
444 if (sigil_is_string(args[1])) {
445 SigilString *s = (SigilString *)sigil_as_ptr(args[1]);
446 msg_data = (const unsigned char *)s->data;
447 msg_len = s->byte_length;
448 } else if (sigil_is_bytevector(args[1])) {
449 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);
450 msg_data = bv->data;
451 msg_len = bv->length;
452 } else {
453 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha512-bytes: expected string or bytevector for data");
454 return SIGIL_UNDEFINED;
455 }
457 unsigned char hmac[64];
459 const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
460 int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);
461 if (ret != 0) {
462 return SIGIL_FALSE;
463 }
465 Value result = sigil_make_bytevector(vm, 64);
466 if (sigil_is_bytevector(result)) {
467 memcpy(sigil_bytevector_data(result), hmac, 64);
468 }
469 return result;
472/*
473 * hmac-sha1 key data -> bytevector
474 * Compute HMAC-SHA1 of data using the given key.
475 * Both key and data can be strings or bytevectors.
476 * Returns 20-byte MAC as bytevector.
477 */
478static Value native_hmac_sha1(SigilVM *vm, int argc, Value *args)
480 (void)argc;
482 const unsigned char *key_data;
483 size_t key_len;
484 const unsigned char *msg_data;
485 size_t msg_len;
487 /* Get key */
488 if (sigil_is_string(args[0])) {
489 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
490 key_data = (const unsigned char *)s->data;
491 key_len = s->byte_length;
492 } else if (sigil_is_bytevector(args[0])) {
493 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
494 key_data = bv->data;
495 key_len = bv->length;
496 } else {
497 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha1: expected string or bytevector for key");
498 return SIGIL_UNDEFINED;
499 }
501 /* Get message */
502 if (sigil_is_string(args[1])) {
503 SigilString *s = (SigilString *)sigil_as_ptr(args[1]);
504 msg_data = (const unsigned char *)s->data;
505 msg_len = s->byte_length;
506 } else if (sigil_is_bytevector(args[1])) {
507 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);
508 msg_data = bv->data;
509 msg_len = bv->length;
510 } else {
511 sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha1: expected string or bytevector for data");
512 return SIGIL_UNDEFINED;
513 }
515 unsigned char hmac[20]; /* HMAC-SHA1 produces 20 bytes */
517 const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);
518 int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);
519 if (ret != 0) {
520 return SIGIL_FALSE;
521 }
523 Value result = sigil_make_bytevector(vm, 20);
524 if (sigil_is_bytevector(result)) {
525 memcpy(sigil_bytevector_data(result), hmac, 20);
526 }
527 return result;
530/*
531 * pbkdf2-sha1 password salt iterations key-length -> bytevector
532 * Derive a key using PBKDF2 with HMAC-SHA1.
533 * Password and salt can be strings or bytevectors.
534 * Returns derived key as bytevector.
535 */
536static Value native_pbkdf2_sha1(SigilVM *vm, int argc, Value *args)
538 (void)argc;
540 const unsigned char *password;
541 size_t password_len;
542 const unsigned char *salt;
543 size_t salt_len;
545 /* Get password */
546 if (sigil_is_string(args[0])) {
547 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
548 password = (const unsigned char *)s->data;
549 password_len = s->byte_length;
550 } else if (sigil_is_bytevector(args[0])) {
551 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
552 password = bv->data;
553 password_len = bv->length;
554 } else {
555 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected string or bytevector for password");
556 return SIGIL_UNDEFINED;
557 }
559 /* Get salt */
560 if (sigil_is_string(args[1])) {
561 SigilString *s = (SigilString *)sigil_as_ptr(args[1]);
562 salt = (const unsigned char *)s->data;
563 salt_len = s->byte_length;
564 } else if (sigil_is_bytevector(args[1])) {
565 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);
566 salt = bv->data;
567 salt_len = bv->length;
568 } else {
569 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected string or bytevector for salt");
570 return SIGIL_UNDEFINED;
571 }
573 /* Get iterations */
574 if (!sigil_is_fixnum(args[2])) {
575 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected integer for iterations");
576 return SIGIL_UNDEFINED;
577 }
578 int iterations = (int)sigil_as_fixnum(args[2]);
579 if (iterations < 1) {
580 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha1: iterations must be positive");
581 return SIGIL_UNDEFINED;
582 }
584 /* Get key length */
585 if (!sigil_is_fixnum(args[3])) {
586 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected integer for key-length");
587 return SIGIL_UNDEFINED;
588 }
589 int key_length = (int)sigil_as_fixnum(args[3]);
590 if (key_length < 1 || key_length > 65536) {
591 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha1: key-length must be 1-65536");
592 return SIGIL_UNDEFINED;
593 }
595 unsigned char *output = malloc(key_length);
596 if (!output) return SIGIL_FALSE;
598 int ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA1,
599 password, password_len,
600 salt, salt_len,
601 iterations, key_length, output);
602 if (ret != 0) {
603 free(output);
604 return SIGIL_FALSE;
605 }
607 Value result = sigil_make_bytevector(vm, key_length);
608 if (sigil_is_bytevector(result)) {
609 memcpy(sigil_bytevector_data(result), output, key_length);
610 }
611 free(output);
612 return result;
615/*
616 * pbkdf2-sha256 password salt iterations key-length -> bytevector
617 * Derive a key using PBKDF2 with HMAC-SHA256.
618 * Password and salt can be strings or bytevectors.
619 * Returns derived key as bytevector.
620 *
621 * Required for SCRAM-SHA-256 (RFC 5802 / RFC 7677): the salted password
622 * `Hi(password, salt, iterations)` is PBKDF2-SHA-256 of the user's
623 * password against the per-user salt, with iterations chosen by the
624 * server (typically 4096+).
625 */
626static Value native_pbkdf2_sha256(SigilVM *vm, int argc, Value *args)
628 (void)argc;
630 const unsigned char *password;
631 size_t password_len;
632 const unsigned char *salt;
633 size_t salt_len;
635 if (sigil_is_string(args[0])) {
636 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
637 password = (const unsigned char *)s->data;
638 password_len = s->byte_length;
639 } else if (sigil_is_bytevector(args[0])) {
640 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
641 password = bv->data;
642 password_len = bv->length;
643 } else {
644 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected string or bytevector for password");
645 return SIGIL_UNDEFINED;
646 }
648 if (sigil_is_string(args[1])) {
649 SigilString *s = (SigilString *)sigil_as_ptr(args[1]);
650 salt = (const unsigned char *)s->data;
651 salt_len = s->byte_length;
652 } else if (sigil_is_bytevector(args[1])) {
653 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);
654 salt = bv->data;
655 salt_len = bv->length;
656 } else {
657 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected string or bytevector for salt");
658 return SIGIL_UNDEFINED;
659 }
661 if (!sigil_is_fixnum(args[2])) {
662 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected integer for iterations");
663 return SIGIL_UNDEFINED;
664 }
665 int iterations = (int)sigil_as_fixnum(args[2]);
666 if (iterations < 1) {
667 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha256: iterations must be positive");
668 return SIGIL_UNDEFINED;
669 }
671 if (!sigil_is_fixnum(args[3])) {
672 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected integer for key-length");
673 return SIGIL_UNDEFINED;
674 }
675 int key_length = (int)sigil_as_fixnum(args[3]);
676 if (key_length < 1 || key_length > 65536) {
677 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha256: key-length must be 1-65536");
678 return SIGIL_UNDEFINED;
679 }
681 unsigned char *output = malloc(key_length);
682 if (!output) return SIGIL_FALSE;
684 int ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA256,
685 password, password_len,
686 salt, salt_len,
687 iterations, key_length, output);
688 if (ret != 0) {
689 free(output);
690 return SIGIL_FALSE;
691 }
693 Value result = sigil_make_bytevector(vm, key_length);
694 if (sigil_is_bytevector(result)) {
695 memcpy(sigil_bytevector_data(result), output, key_length);
696 }
697 free(output);
698 return result;
701/*
702 * pbkdf2-sha512 password salt iterations key-length -> bytevector
703 * Derive a key using PBKDF2 with HMAC-SHA512.
704 * Password and salt can be strings or bytevectors.
705 * Returns derived key as bytevector.
706 */
707static Value native_pbkdf2_sha512(SigilVM *vm, int argc, Value *args)
709 (void)argc;
711 const unsigned char *password;
712 size_t password_len;
713 const unsigned char *salt;
714 size_t salt_len;
716 if (sigil_is_string(args[0])) {
717 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
718 password = (const unsigned char *)s->data;
719 password_len = s->byte_length;
720 } else if (sigil_is_bytevector(args[0])) {
721 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
722 password = bv->data;
723 password_len = bv->length;
724 } else {
725 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected string or bytevector for password");
726 return SIGIL_UNDEFINED;
727 }
729 if (sigil_is_string(args[1])) {
730 SigilString *s = (SigilString *)sigil_as_ptr(args[1]);
731 salt = (const unsigned char *)s->data;
732 salt_len = s->byte_length;
733 } else if (sigil_is_bytevector(args[1])) {
734 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);
735 salt = bv->data;
736 salt_len = bv->length;
737 } else {
738 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected string or bytevector for salt");
739 return SIGIL_UNDEFINED;
740 }
742 if (!sigil_is_fixnum(args[2])) {
743 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected integer for iterations");
744 return SIGIL_UNDEFINED;
745 }
746 int iterations = (int)sigil_as_fixnum(args[2]);
747 if (iterations < 1) {
748 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha512: iterations must be positive");
749 return SIGIL_UNDEFINED;
750 }
752 if (!sigil_is_fixnum(args[3])) {
753 sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected integer for key-length");
754 return SIGIL_UNDEFINED;
755 }
756 int key_length = (int)sigil_as_fixnum(args[3]);
757 if (key_length < 1 || key_length > 65536) {
758 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha512: key-length must be 1-65536");
759 return SIGIL_UNDEFINED;
760 }
762 unsigned char *output = malloc(key_length);
763 if (!output) return SIGIL_FALSE;
765 int ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA512,
766 password, password_len,
767 salt, salt_len,
768 iterations, key_length, output);
769 if (ret != 0) {
770 free(output);
771 return SIGIL_FALSE;
772 }
774 Value result = sigil_make_bytevector(vm, key_length);
775 if (sigil_is_bytevector(result)) {
776 memcpy(sigil_bytevector_data(result), output, key_length);
777 }
778 free(output);
779 return result;
782/*
783 * base64-encode data -> string
784 * Encode data (string or bytevector) as base64.
785 */
786static Value native_base64_encode(SigilVM *vm, int argc, Value *args)
788 (void)argc;
790 const unsigned char *data;
791 size_t len;
793 if (sigil_is_string(args[0])) {
794 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
795 data = (const unsigned char *)s->data;
796 len = s->byte_length;
797 } else if (sigil_is_bytevector(args[0])) {
798 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);
799 data = bv->data;
800 len = bv->length;
801 } else {
802 sigil__vm_error(vm, SIGIL_ERR_TYPE, "base64-encode: expected string or bytevector");
803 return SIGIL_UNDEFINED;
804 }
806 /* Calculate output size: 4 * ceil(len/3) + 1 for null terminator */
807 size_t out_len = ((len + 2) / 3) * 4 + 1;
808 char *output = malloc(out_len);
809 if (!output) return SIGIL_FALSE;
811 /* TODO: Implement when mbedTLS is added
812 size_t olen;
813 int ret = mbedtls_base64_encode((unsigned char *)output, out_len, &olen, data, len);
814 if (ret != 0) {
815 free(output);
816 return SIGIL_FALSE;
817 }
818 Value result = sigil_make_string(vm, output, olen);
819 */
821 /* Temporary stub: use simple base64 implementation */
822 static const char base64_chars[] =
823 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
825 size_t i = 0, j = 0;
826 unsigned char array3[3], array4[4];
828 while (len--) {
829 array3[i++] = *(data++);
830 if (i == 3) {
831 array4[0] = (array3[0] & 0xfc) >> 2;
832 array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);
833 array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);
834 array4[3] = array3[2] & 0x3f;
836 for (i = 0; i < 4; i++)
837 output[j++] = base64_chars[array4[i]];
838 i = 0;
839 }
840 }
842 if (i) {
843 for (size_t k = i; k < 3; k++)
844 array3[k] = '\0';
846 array4[0] = (array3[0] & 0xfc) >> 2;
847 array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);
848 array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);
850 for (size_t k = 0; k < i + 1; k++)
851 output[j++] = base64_chars[array4[k]];
853 while (i++ < 3)
854 output[j++] = '=';
855 }
857 output[j] = '\0';
858 Value result = sigil_make_string(vm, output, j);
859 free(output);
860 return result;
863/*
864 * base64-decode string -> bytevector | #f
865 * Decode base64 string to bytevector.
866 * Returns #f if input is not valid base64.
867 */
868static Value native_base64_decode(SigilVM *vm, int argc, Value *args)
870 (void)argc;
872 if (!sigil_is_string(args[0])) {
873 sigil__vm_error(vm, SIGIL_ERR_TYPE, "base64-decode: expected string");
874 return SIGIL_UNDEFINED;
875 }
877 SigilString *s = (SigilString *)sigil_as_ptr(args[0]);
878 const char *data = s->data;
879 size_t len = s->byte_length;
881 /* Skip trailing whitespace */
882 while (len > 0 && (data[len-1] == ' ' || data[len-1] == '\n' ||
883 data[len-1] == '\r' || data[len-1] == '\t')) {
884 len--;
885 }
887 if (len == 0) {
888 return sigil_make_bytevector(vm, 0);
889 }
891 /* Calculate output size: 3 * (len/4) */
892 size_t out_len = (len / 4) * 3;
893 if (len > 0 && data[len-1] == '=') out_len--;
894 if (len > 1 && data[len-2] == '=') out_len--;
896 unsigned char *output = malloc(out_len + 1);
897 if (!output) return SIGIL_FALSE;
899 /* TODO: Implement when mbedTLS is added
900 size_t olen;
901 int ret = mbedtls_base64_decode(output, out_len + 1, &olen,
902 (const unsigned char *)data, len);
903 if (ret != 0) {
904 free(output);
905 return SIGIL_FALSE;
906 }
907 Value result = sigil_make_bytevector(vm, output, olen);
908 */
910 /* Simple base64 decode implementation */
911 static const unsigned char base64_table[256] = {
912 ['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5,
913 ['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11,
914 ['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17,
915 ['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23,
916 ['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29,
917 ['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35,
918 ['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41,
919 ['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47,
920 ['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53,
921 ['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59,
922 ['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63
923 };
925 size_t i = 0, j = 0;
926 unsigned char array4[4], array3[3];
927 int k = 0;
929 while (i < len) {
930 if (data[i] == '=' || data[i] == '\n' || data[i] == '\r' ||
931 data[i] == ' ' || data[i] == '\t') {
932 i++;
933 continue;
934 }
936 array4[k++] = base64_table[(unsigned char)data[i++]];
938 if (k == 4) {
939 array3[0] = (array4[0] << 2) + ((array4[1] & 0x30) >> 4);
940 array3[1] = ((array4[1] & 0x0f) << 4) + ((array4[2] & 0x3c) >> 2);
941 array3[2] = ((array4[2] & 0x03) << 6) + array4[3];
943 for (k = 0; k < 3 && j < out_len; k++)
944 output[j++] = array3[k];
945 k = 0;
946 }
947 }
949 if (k) {
950 for (int m = k; m < 4; m++)
951 array4[m] = 0;
953 array3[0] = (array4[0] << 2) + ((array4[1] & 0x30) >> 4);
954 array3[1] = ((array4[1] & 0x0f) << 4) + ((array4[2] & 0x3c) >> 2);
956 for (int m = 0; m < k - 1 && j < out_len; m++)
957 output[j++] = array3[m];
958 }
960 Value result = sigil_make_bytevector(vm, j);
961 if (sigil_is_bytevector(result)) {
962 memcpy(sigil_bytevector_data(result), output, j);
963 }
964 free(output);
965 return result;
968/*
969 * random-bytes count -> bytevector
970 * Generate cryptographically secure random bytes.
971 * Uses mbedTLS CTR-DRBG with OS entropy.
972 */
973static Value native_random_bytes(SigilVM *vm, int argc, Value *args)
975 (void)argc;
977 if (!sigil_is_fixnum(args[0])) {
978 sigil__vm_error(vm, SIGIL_ERR_TYPE, "random-bytes: expected integer");
979 return SIGIL_UNDEFINED;
980 }
982 int64_t count = sigil_as_fixnum(args[0]);
983 if (count < 0) {
984 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: count must be non-negative");
985 return SIGIL_UNDEFINED;
986 }
987 if (count > 65536) {
988 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: count exceeds maximum (65536)");
989 return SIGIL_UNDEFINED;
990 }
992 if (ensure_rng_initialized() != 0) {
993 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: failed to initialize RNG");
994 return SIGIL_UNDEFINED;
995 }
997 Value bv = sigil_make_bytevector(vm, (size_t)count);
998 if (bv == SIGIL_UNDEFINED) return bv;
1000 unsigned char *data = sigil_bytevector_data(bv);
1001 int ret = mbedtls_ctr_drbg_random(&ctr_drbg_ctx, data, (size_t)count);
1002 if (ret != 0) {
1003 sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: RNG failed");
1004 return SIGIL_UNDEFINED;
1007 return bv;
1010/* ===========================================================
1011 * mbedTLS MPI — Big Integer Arithmetic
1013 * Exposes mbedtls_mpi for arbitrary-precision integer operations
1014 * beyond the 63-bit fixnum range. Bytevectors in big-endian.
1015 * Functions allocate mpi contexts internally; no GC-visible handles.
1016 * =========================================================== */
1018static int mpi_read_bv(mbedtls_mpi *X, Value bv_val)
1020 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(bv_val);
1021 return mbedtls_mpi_read_binary(X, bv->data, bv->length);
1024static Value mpi_write_bv(SigilVM *vm, mbedtls_mpi *X, size_t size)
1026 Value result = sigil_make_bytevector(vm, size);
1027 if (!sigil_is_bytevector(result)) return SIGIL_FALSE;
1028 int ret = mbedtls_mpi_write_binary(X, sigil_bytevector_data(result), size);
1029 if (ret != 0) return SIGIL_FALSE;
1030 return result;
1033/* MPI_BINOP generates add, sub, mul — the only difference is the op. */
1034#define MPI_BINOP(method_name, op_fn) \
1035static Value native_mpi_##method_name(SigilVM *vm, int argc, Value *args) \
1036{ \
1037 (void)argc; \
1038 if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) { \
1039 sigil__vm_error(vm, SIGIL_ERR_TYPE, \
1040 "mpi-" #method_name ": expected bytevectors"); \
1041 return SIGIL_UNDEFINED; \
1042 } \
1043 if (!sigil_is_fixnum(args[2])) { \
1044 sigil__vm_error(vm, SIGIL_ERR_TYPE, \
1045 "mpi-" #method_name ": expected integer size"); \
1046 return SIGIL_UNDEFINED; \
1047 } \
1048 size_t size = (size_t)sigil_as_fixnum(args[2]); \
1049 mbedtls_mpi A, B, R; \
1050 mbedtls_mpi_init(&A); mbedtls_mpi_init(&B); mbedtls_mpi_init(&R); \
1051 Value result = SIGIL_FALSE; \
1052 if (mpi_read_bv(&A, args[0]) != 0) goto cleanup; \
1053 if (mpi_read_bv(&B, args[1]) != 0) goto cleanup; \
1054 if (op_fn(&R, &A, &B) != 0) goto cleanup; \
1055 result = mpi_write_bv(vm, &R, size); \
1056cleanup: \
1057 mbedtls_mpi_free(&R); mbedtls_mpi_free(&B); mbedtls_mpi_free(&A); \
1058 return result; \
1061MPI_BINOP(add, mbedtls_mpi_add_mpi)
1062MPI_BINOP(sub, mbedtls_mpi_sub_mpi)
1063MPI_BINOP(mul, mbedtls_mpi_mul_mpi)
1065#undef MPI_BINOP
1068 * mpi-div a-bv b-bv size -> (cons quotient-bv remainder-bv)
1069 */
1070static Value native_mpi_div(SigilVM *vm, int argc, Value *args)
1072 (void)argc;
1074 if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {
1075 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-div: expected bytevectors");
1076 return SIGIL_UNDEFINED;
1078 if (!sigil_is_fixnum(args[2])) {
1079 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-div: expected integer size");
1080 return SIGIL_UNDEFINED;
1083 size_t size = (size_t)sigil_as_fixnum(args[2]);
1085 mbedtls_mpi A, B, Q, R;
1086 mbedtls_mpi_init(&A); mbedtls_mpi_init(&B);
1087 mbedtls_mpi_init(&Q); mbedtls_mpi_init(&R);
1089 Value result = SIGIL_FALSE;
1091 if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;
1092 if (mpi_read_bv(&B, args[1]) != 0) goto cleanup;
1093 if (mbedtls_mpi_div_mpi(&Q, &R, &A, &B) != 0) goto cleanup;
1095 Value q_bv = mpi_write_bv(vm, &Q, size);
1096 Value r_bv = mpi_write_bv(vm, &R, size);
1097 if (!sigil_is_bytevector(q_bv) || !sigil_is_bytevector(r_bv)) goto cleanup;
1099 result = sigil_cons(vm, q_bv, r_bv);
1101cleanup:
1102 mbedtls_mpi_free(&R); mbedtls_mpi_free(&Q);
1103 mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);
1104 return result;
1108 * mpi-mod a-bv n-bv size -> bytevector
1109 */
1110static Value native_mpi_mod(SigilVM *vm, int argc, Value *args)
1112 (void)argc;
1114 if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {
1115 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod: expected bytevectors");
1116 return SIGIL_UNDEFINED;
1118 if (!sigil_is_fixnum(args[2])) {
1119 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod: expected integer size");
1120 return SIGIL_UNDEFINED;
1123 size_t size = (size_t)sigil_as_fixnum(args[2]);
1125 mbedtls_mpi A, N, R;
1126 mbedtls_mpi_init(&A); mbedtls_mpi_init(&N); mbedtls_mpi_init(&R);
1128 Value result = SIGIL_FALSE;
1130 if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;
1131 if (mpi_read_bv(&N, args[1]) != 0) goto cleanup;
1132 if (mbedtls_mpi_mod_mpi(&R, &A, &N) != 0) goto cleanup;
1134 result = mpi_write_bv(vm, &R, size);
1136cleanup:
1137 mbedtls_mpi_free(&R); mbedtls_mpi_free(&N); mbedtls_mpi_free(&A);
1138 return result;
1142 * mpi-mod-add a-bv b-bv n-bv size -> bytevector
1143 * (a + b) mod n
1144 */
1145static Value native_mpi_mod_add(SigilVM *vm, int argc, Value *args)
1147 (void)argc;
1149 if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])
1150 || !sigil_is_bytevector(args[2])) {
1151 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod-add: expected bytevectors");
1152 return SIGIL_UNDEFINED;
1154 if (!sigil_is_fixnum(args[3])) {
1155 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod-add: expected integer size");
1156 return SIGIL_UNDEFINED;
1159 size_t size = (size_t)sigil_as_fixnum(args[3]);
1161 mbedtls_mpi A, B, N, R;
1162 mbedtls_mpi_init(&A); mbedtls_mpi_init(&B);
1163 mbedtls_mpi_init(&N); mbedtls_mpi_init(&R);
1165 Value result = SIGIL_FALSE;
1167 if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;
1168 if (mpi_read_bv(&B, args[1]) != 0) goto cleanup;
1169 if (mpi_read_bv(&N, args[2]) != 0) goto cleanup;
1170 if (mbedtls_mpi_add_mpi(&R, &A, &B) != 0) goto cleanup;
1171 if (mbedtls_mpi_mod_mpi(&R, &R, &N) != 0) goto cleanup;
1173 result = mpi_write_bv(vm, &R, size);
1175cleanup:
1176 mbedtls_mpi_free(&R); mbedtls_mpi_free(&N);
1177 mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);
1178 return result;
1182 * mpi-inv-mod a-bv n-bv size -> bytevector | #f
1183 */
1184static Value native_mpi_inv_mod(SigilVM *vm, int argc, Value *args)
1186 (void)argc;
1188 if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {
1189 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-inv-mod: expected bytevectors");
1190 return SIGIL_UNDEFINED;
1192 if (!sigil_is_fixnum(args[2])) {
1193 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-inv-mod: expected integer size");
1194 return SIGIL_UNDEFINED;
1197 size_t size = (size_t)sigil_as_fixnum(args[2]);
1199 mbedtls_mpi A, N, R;
1200 mbedtls_mpi_init(&A); mbedtls_mpi_init(&N); mbedtls_mpi_init(&R);
1202 Value result = SIGIL_FALSE;
1204 if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;
1205 if (mpi_read_bv(&N, args[1]) != 0) goto cleanup;
1206 if (mbedtls_mpi_inv_mod(&R, &A, &N) != 0) goto cleanup;
1208 result = mpi_write_bv(vm, &R, size);
1210cleanup:
1211 mbedtls_mpi_free(&R); mbedtls_mpi_free(&N); mbedtls_mpi_free(&A);
1212 return result;
1216 * mpi-cmp a-bv b-bv -> fixnum (-1, 0, or 1)
1217 */
1218static Value native_mpi_cmp(SigilVM *vm, int argc, Value *args)
1220 (void)argc;
1222 if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {
1223 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-cmp: expected bytevectors");
1224 return SIGIL_UNDEFINED;
1227 mbedtls_mpi A, B;
1228 mbedtls_mpi_init(&A); mbedtls_mpi_init(&B);
1230 if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;
1231 if (mpi_read_bv(&B, args[1]) != 0) goto cleanup;
1233 int cmp = mbedtls_mpi_cmp_mpi(&A, &B);
1234 mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);
1235 return sigil_fixnum(cmp);
1237cleanup:
1238 mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);
1239 return SIGIL_FALSE;
1243 * mpi-is-zero? a-bv -> boolean
1244 */
1245static Value native_mpi_is_zero(SigilVM *vm, int argc, Value *args)
1247 (void)argc;
1249 if (!sigil_is_bytevector(args[0])) {
1250 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-is-zero?: expected bytevector");
1251 return SIGIL_UNDEFINED;
1254 mbedtls_mpi A;
1255 mbedtls_mpi_init(&A);
1257 if (mpi_read_bv(&A, args[0]) != 0) {
1258 mbedtls_mpi_free(&A);
1259 return SIGIL_FALSE;
1262 int result = (mbedtls_mpi_cmp_int(&A, 0) == 0);
1263 mbedtls_mpi_free(&A);
1264 return result ? SIGIL_TRUE : SIGIL_FALSE;
1268 * mpi-shift-l a-bv bits size -> bytevector
1269 */
1270static Value native_mpi_shift_l(SigilVM *vm, int argc, Value *args)
1272 (void)argc;
1274 if (!sigil_is_bytevector(args[0])) {
1275 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-l: expected bytevector");
1276 return SIGIL_UNDEFINED;
1278 if (!sigil_is_fixnum(args[1]) || !sigil_is_fixnum(args[2])) {
1279 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-l: expected integer arguments");
1280 return SIGIL_UNDEFINED;
1283 int64_t bits_in = sigil_as_fixnum(args[1]);
1284 int64_t size_in = sigil_as_fixnum(args[2]);
1285 if (bits_in < 0 || size_in < 0) {
1286 sigil__vm_error(vm, SIGIL_ERR_TYPE,
1287 "mpi-shift-l: bits and size must be non-negative");
1288 return SIGIL_UNDEFINED;
1291 size_t bits = (size_t)bits_in;
1292 size_t size = (size_t)size_in;
1294 mbedtls_mpi A;
1295 mbedtls_mpi_init(&A);
1297 if (mpi_read_bv(&A, args[0]) != 0) {
1298 mbedtls_mpi_free(&A);
1299 return SIGIL_FALSE;
1302 if (mbedtls_mpi_shift_l(&A, bits) != 0) {
1303 mbedtls_mpi_free(&A);
1304 return SIGIL_FALSE;
1307 Value result = mpi_write_bv(vm, &A, size);
1308 mbedtls_mpi_free(&A);
1309 return result;
1313 * mpi-shift-r a-bv bits -> bytevector
1314 */
1315static Value native_mpi_shift_r(SigilVM *vm, int argc, Value *args)
1317 (void)argc;
1319 if (!sigil_is_bytevector(args[0])) {
1320 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-r: expected bytevector");
1321 return SIGIL_UNDEFINED;
1323 if (!sigil_is_fixnum(args[1])) {
1324 sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-r: expected integer");
1325 return SIGIL_UNDEFINED;
1328 int64_t bits_in = sigil_as_fixnum(args[1]);
1329 if (bits_in < 0) {
1330 sigil__vm_error(vm, SIGIL_ERR_TYPE,
1331 "mpi-shift-r: bits must be non-negative");
1332 return SIGIL_UNDEFINED;
1335 size_t bits = (size_t)bits_in;
1337 mbedtls_mpi A;
1338 mbedtls_mpi_init(&A);
1340 if (mpi_read_bv(&A, args[0]) != 0) {
1341 mbedtls_mpi_free(&A);
1342 return SIGIL_FALSE;
1345 if (mbedtls_mpi_shift_r(&A, bits) != 0) {
1346 mbedtls_mpi_free(&A);
1347 return SIGIL_FALSE;
1350 SigilBytevector *in_bv = (SigilBytevector *)sigil_as_ptr(args[0]);
1351 Value result = mpi_write_bv(vm, &A, in_bv->length);
1352 mbedtls_mpi_free(&A);
1353 return result;
1356/* ===========================================================
1357 * ECDSA P-256, ECDH P-256, AES-128-GCM
1359 * Used by Web Push (RFC 8291 / RFC 8292): VAPID JWT signs with
1360 * ES256 (ECDSA P-256 + SHA-256), payload encryption derives
1361 * shared secret via ECDH P-256 and seals with AES-128-GCM.
1362 * =========================================================== */
1364#define ECDSA_P256_PRIV_LEN 32
1365#define ECDSA_P256_PUB_LEN 65 /* Uncompressed: 0x04 || X(32) || Y(32) */
1366#define ECDSA_P256_SIG_LEN 64 /* JOSE format: r(32) || s(32) */
1367#define ECDH_P256_SECRET_LEN 32
1370 * Extract bytes from a string-or-bytevector argument.
1371 * On type mismatch raises a VM error and returns 0.
1372 */
1373static int crypto_read_bytes(SigilVM *vm, Value v, const char *fn,
1374 const unsigned char **out_data, size_t *out_len)
1376 if (sigil_is_string(v)) {
1377 SigilString *s = (SigilString *)sigil_as_ptr(v);
1378 *out_data = (const unsigned char *)s->data;
1379 *out_len = s->byte_length;
1380 return 1;
1382 if (sigil_is_bytevector(v)) {
1383 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(v);
1384 *out_data = bv->data;
1385 *out_len = bv->length;
1386 return 1;
1388 sigil__vm_error(vm, SIGIL_ERR_TYPE,
1389 "expected string or bytevector argument");
1390 (void)fn;
1391 return 0;
1395 * Extract bytes from a bytevector-only argument with a required length.
1396 * Returns 0 on type mismatch or wrong length (raises VM error).
1397 */
1398static int crypto_read_bv_exact(SigilVM *vm, Value v, size_t want,
1399 const char *what,
1400 const unsigned char **out_data)
1402 if (!sigil_is_bytevector(v)) {
1403 sigil__vm_error(vm, SIGIL_ERR_TYPE,
1404 "expected bytevector argument");
1405 (void)what;
1406 return 0;
1408 SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(v);
1409 if (bv->length != want) {
1410 sigil__vm_error(vm, SIGIL_ERR_RUNTIME,
1411 "wrong bytevector length");
1412 return 0;
1414 *out_data = bv->data;
1415 return 1;
1419 * ecdsa-p256-generate-keypair -> (cons priv-bv-32 pub-bv-65)
1421 * Generates a fresh P-256 keypair. priv is the 32-byte big-endian
1422 * scalar; pub is the 65-byte uncompressed-point encoding suitable
1423 * for VAPID's `applicationServerKey` and for ECDH peer-key input.
1424 */
1425static Value native_ecdsa_p256_generate_keypair(SigilVM *vm, int argc, Value *args)
1427 (void)argc; (void)args;
1429 if (ensure_rng_initialized() != 0) {
1430 sigil__vm_error(vm, SIGIL_ERR_RUNTIME,
1431 "ecdsa-p256-generate-keypair: failed to init RNG");
1432 return SIGIL_UNDEFINED;
1435 mbedtls_ecp_group grp;
1436 mbedtls_mpi d;
1437 mbedtls_ecp_point Q;
1438 mbedtls_ecp_group_init(&grp);
1439 mbedtls_mpi_init(&d);
1440 mbedtls_ecp_point_init(&Q);
1442 Value result = SIGIL_FALSE;
1443 int ret;
1444 unsigned char priv_buf[ECDSA_P256_PRIV_LEN];
1445 unsigned char pub_buf[ECDSA_P256_PUB_LEN];
1446 size_t pub_olen = 0;
1448 ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
1449 if (ret != 0) goto cleanup;
1451 ret = mbedtls_ecp_gen_keypair(&grp, &d, &Q,
1452 mbedtls_ctr_drbg_random, &ctr_drbg_ctx);
1453 if (ret != 0) goto cleanup;
1455 ret = mbedtls_mpi_write_binary(&d, priv_buf, ECDSA_P256_PRIV_LEN);
1456 if (ret != 0) goto cleanup;
1458 ret = mbedtls_ecp_point_write_binary(&grp, &Q,
1459 MBEDTLS_ECP_PF_UNCOMPRESSED,
1460 &pub_olen, pub_buf,
1461 ECDSA_P256_PUB_LEN);
1462 if (ret != 0 || pub_olen != ECDSA_P256_PUB_LEN) goto cleanup;
1464 Value priv_bv = sigil_make_bytevector(vm, ECDSA_P256_PRIV_LEN);
1465 Value pub_bv = sigil_make_bytevector(vm, ECDSA_P256_PUB_LEN);
1466 if (!sigil_is_bytevector(priv_bv) || !sigil_is_bytevector(pub_bv)) {
1467 goto cleanup;
1469 memcpy(sigil_bytevector_data(priv_bv), priv_buf, ECDSA_P256_PRIV_LEN);
1470 memcpy(sigil_bytevector_data(pub_bv), pub_buf, ECDSA_P256_PUB_LEN);
1472 result = sigil_cons(vm, priv_bv, pub_bv);
1474cleanup:
1475 /* Wipe stack copies of private material before unwinding. */
1476 memset(priv_buf, 0, sizeof(priv_buf));
1477 mbedtls_ecp_point_free(&Q);
1478 mbedtls_mpi_free(&d);
1479 mbedtls_ecp_group_free(&grp);
1480 return result;
1484 * ecdsa-p256-sign priv-bv message -> sig-bv-64 | #f
1486 * Hashes `message` with SHA-256, signs with ECDSA P-256 using the
1487 * provided 32-byte private scalar, and returns the JOSE-format
1488 * 64-byte signature (r || s, each 32 bytes big-endian). This is
1489 * the format VAPID JWT (ES256) wants — NOT DER. Returns #f if
1490 * the private key is invalid.
1491 */
1492static Value native_ecdsa_p256_sign(SigilVM *vm, int argc, Value *args)
1494 (void)argc;
1496 const unsigned char *priv_data;
1497 if (!crypto_read_bv_exact(vm, args[0], ECDSA_P256_PRIV_LEN,
1498 "private key", &priv_data)) {
1499 return SIGIL_UNDEFINED;
1502 const unsigned char *msg_data;
1503 size_t msg_len;
1504 if (!crypto_read_bytes(vm, args[1], "ecdsa-p256-sign",
1505 &msg_data, &msg_len)) {
1506 return SIGIL_UNDEFINED;
1509 if (ensure_rng_initialized() != 0) {
1510 sigil__vm_error(vm, SIGIL_ERR_RUNTIME,
1511 "ecdsa-p256-sign: failed to init RNG");
1512 return SIGIL_UNDEFINED;
1515 /* SHA-256 the message into a 32-byte digest. */
1516 unsigned char digest[32];
1517 mbedtls_sha256_context sha;
1518 mbedtls_sha256_init(&sha);
1519 mbedtls_sha256_starts(&sha, 0);
1520 mbedtls_sha256_update(&sha, msg_data, msg_len);
1521 mbedtls_sha256_finish(&sha, digest);
1522 mbedtls_sha256_free(&sha);
1524 mbedtls_ecp_group grp;
1525 mbedtls_mpi d, r, s;
1526 mbedtls_ecp_group_init(&grp);
1527 mbedtls_mpi_init(&d);
1528 mbedtls_mpi_init(&r);
1529 mbedtls_mpi_init(&s);
1531 Value result = SIGIL_FALSE;
1532 int ret;
1533 unsigned char sig_buf[ECDSA_P256_SIG_LEN];
1535 ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
1536 if (ret != 0) goto cleanup;
1538 ret = mbedtls_mpi_read_binary(&d, priv_data, ECDSA_P256_PRIV_LEN);
1539 if (ret != 0) goto cleanup;
1541 /* Reject scalars outside [1, n-1] — mbedTLS doesn't validate
1542 * for sign(); a zero d would silently produce an invalid sig. */
1543 if (mbedtls_ecp_check_privkey(&grp, &d) != 0) goto cleanup;
1545 ret = mbedtls_ecdsa_sign(&grp, &r, &s, &d,
1546 digest, sizeof(digest),
1547 mbedtls_ctr_drbg_random, &ctr_drbg_ctx);
1548 if (ret != 0) goto cleanup;
1550 ret = mbedtls_mpi_write_binary(&r, sig_buf, 32);
1551 if (ret != 0) goto cleanup;
1552 ret = mbedtls_mpi_write_binary(&s, sig_buf + 32, 32);
1553 if (ret != 0) goto cleanup;
1555 result = sigil_make_bytevector(vm, ECDSA_P256_SIG_LEN);
1556 if (sigil_is_bytevector(result)) {
1557 memcpy(sigil_bytevector_data(result), sig_buf, ECDSA_P256_SIG_LEN);
1560cleanup:
1561 mbedtls_mpi_free(&s);
1562 mbedtls_mpi_free(&r);
1563 mbedtls_mpi_free(&d);
1564 mbedtls_ecp_group_free(&grp);
1565 return result;
1569 * ecdsa-p256-verify pub-bv message sig-bv -> boolean
1571 * Returns #t when the JOSE-format 64-byte sig validates against
1572 * the message under the given 65-byte uncompressed-point public key,
1573 * otherwise #f. Hashes the message with SHA-256 internally so the
1574 * caller passes the raw message body (matches sign's input shape).
1575 */
1576static Value native_ecdsa_p256_verify(SigilVM *vm, int argc, Value *args)
1578 (void)argc;
1580 const unsigned char *pub_data;
1581 if (!crypto_read_bv_exact(vm, args[0], ECDSA_P256_PUB_LEN,
1582 "public key", &pub_data)) {
1583 return SIGIL_UNDEFINED;
1586 const unsigned char *msg_data;
1587 size_t msg_len;
1588 if (!crypto_read_bytes(vm, args[1], "ecdsa-p256-verify",
1589 &msg_data, &msg_len)) {
1590 return SIGIL_UNDEFINED;
1593 const unsigned char *sig_data;
1594 if (!crypto_read_bv_exact(vm, args[2], ECDSA_P256_SIG_LEN,
1595 "signature", &sig_data)) {
1596 return SIGIL_UNDEFINED;
1599 unsigned char digest[32];
1600 mbedtls_sha256_context sha;
1601 mbedtls_sha256_init(&sha);
1602 mbedtls_sha256_starts(&sha, 0);
1603 mbedtls_sha256_update(&sha, msg_data, msg_len);
1604 mbedtls_sha256_finish(&sha, digest);
1605 mbedtls_sha256_free(&sha);
1607 mbedtls_ecp_group grp;
1608 mbedtls_ecp_point Q;
1609 mbedtls_mpi r, s;
1610 mbedtls_ecp_group_init(&grp);
1611 mbedtls_ecp_point_init(&Q);
1612 mbedtls_mpi_init(&r);
1613 mbedtls_mpi_init(&s);
1615 Value result = SIGIL_FALSE;
1616 int ret;
1618 ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
1619 if (ret != 0) goto cleanup;
1621 ret = mbedtls_ecp_point_read_binary(&grp, &Q, pub_data, ECDSA_P256_PUB_LEN);
1622 if (ret != 0) goto cleanup;
1624 /* Reject points off the curve / at infinity to avoid invalid-curve
1625 * attacks — point_read_binary parses but does not validate. */
1626 if (mbedtls_ecp_check_pubkey(&grp, &Q) != 0) goto cleanup;
1628 ret = mbedtls_mpi_read_binary(&r, sig_data, 32);
1629 if (ret != 0) goto cleanup;
1630 ret = mbedtls_mpi_read_binary(&s, sig_data + 32, 32);
1631 if (ret != 0) goto cleanup;
1633 ret = mbedtls_ecdsa_verify(&grp, digest, sizeof(digest), &Q, &r, &s);
1634 result = (ret == 0) ? SIGIL_TRUE : SIGIL_FALSE;
1636cleanup:
1637 mbedtls_mpi_free(&s);
1638 mbedtls_mpi_free(&r);
1639 mbedtls_ecp_point_free(&Q);
1640 mbedtls_ecp_group_free(&grp);
1641 return result;
1645 * ecdh-p256-shared-secret priv-bv peer-pub-bv -> bytevector(32) | #f
1647 * ECDH on P-256: derives the 32-byte big-endian X coordinate of
1648 * (priv * peer_pub). The shared secret is the raw X coordinate per
1649 * RFC 8291 (Web Push uses this directly as the IKM input to HKDF).
1650 * Validates that peer-pub-bv is a valid point on the curve before
1651 * computing — invalid-curve attack defence.
1652 */
1653static Value native_ecdh_p256_shared_secret(SigilVM *vm, int argc, Value *args)
1655 (void)argc;
1657 const unsigned char *priv_data;
1658 if (!crypto_read_bv_exact(vm, args[0], ECDSA_P256_PRIV_LEN,
1659 "private key", &priv_data)) {
1660 return SIGIL_UNDEFINED;
1663 const unsigned char *peer_data;
1664 if (!crypto_read_bv_exact(vm, args[1], ECDSA_P256_PUB_LEN,
1665 "peer public key", &peer_data)) {
1666 return SIGIL_UNDEFINED;
1669 if (ensure_rng_initialized() != 0) {
1670 sigil__vm_error(vm, SIGIL_ERR_RUNTIME,
1671 "ecdh-p256-shared-secret: failed to init RNG");
1672 return SIGIL_UNDEFINED;
1675 mbedtls_ecp_group grp;
1676 mbedtls_mpi d, z;
1677 mbedtls_ecp_point peer_Q;
1678 mbedtls_ecp_group_init(&grp);
1679 mbedtls_mpi_init(&d);
1680 mbedtls_mpi_init(&z);
1681 mbedtls_ecp_point_init(&peer_Q);
1683 Value result = SIGIL_FALSE;
1684 int ret;
1685 unsigned char secret_buf[ECDH_P256_SECRET_LEN];
1687 ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);
1688 if (ret != 0) goto cleanup;
1690 ret = mbedtls_mpi_read_binary(&d, priv_data, ECDSA_P256_PRIV_LEN);
1691 if (ret != 0) goto cleanup;
1692 if (mbedtls_ecp_check_privkey(&grp, &d) != 0) goto cleanup;
1694 ret = mbedtls_ecp_point_read_binary(&grp, &peer_Q, peer_data,
1695 ECDSA_P256_PUB_LEN);
1696 if (ret != 0) goto cleanup;
1697 if (mbedtls_ecp_check_pubkey(&grp, &peer_Q) != 0) goto cleanup;
1699 ret = mbedtls_ecdh_compute_shared(&grp, &z, &peer_Q, &d,
1700 mbedtls_ctr_drbg_random, &ctr_drbg_ctx);
1701 if (ret != 0) goto cleanup;
1703 ret = mbedtls_mpi_write_binary(&z, secret_buf, ECDH_P256_SECRET_LEN);
1704 if (ret != 0) goto cleanup;
1706 result = sigil_make_bytevector(vm, ECDH_P256_SECRET_LEN);
1707 if (sigil_is_bytevector(result)) {
1708 memcpy(sigil_bytevector_data(result), secret_buf,
1709 ECDH_P256_SECRET_LEN);
1712cleanup:
1713 memset(secret_buf, 0, sizeof(secret_buf));
1714 mbedtls_ecp_point_free(&peer_Q);
1715 mbedtls_mpi_free(&z);
1716 mbedtls_mpi_free(&d);
1717 mbedtls_ecp_group_free(&grp);
1718 return result;
1722 * aes-128-gcm-encrypt key-bv-16 iv-bv-12 aad plaintext
1723 * -> (cons ciphertext-bv tag-bv-16) | #f
1725 * AAD and plaintext accept string or bytevector. Ciphertext length
1726 * matches plaintext length; tag is always 16 bytes (full GCM tag).
1727 * IV must be 12 bytes (the AEAD-recommended length, and what
1728 * RFC 8291 § 3 prescribes).
1729 */
1730static Value native_aes_128_gcm_encrypt(SigilVM *vm, int argc, Value *args)
1732 (void)argc;
1734 const unsigned char *key_data;
1735 if (!crypto_read_bv_exact(vm, args[0], 16, "key", &key_data)) {
1736 return SIGIL_UNDEFINED;
1739 const unsigned char *iv_data;
1740 if (!crypto_read_bv_exact(vm, args[1], 12, "iv", &iv_data)) {
1741 return SIGIL_UNDEFINED;
1744 const unsigned char *aad_data;
1745 size_t aad_len;
1746 if (!crypto_read_bytes(vm, args[2], "aes-128-gcm-encrypt aad",
1747 &aad_data, &aad_len)) {
1748 return SIGIL_UNDEFINED;
1751 const unsigned char *pt_data;
1752 size_t pt_len;
1753 if (!crypto_read_bytes(vm, args[3], "aes-128-gcm-encrypt plaintext",
1754 &pt_data, &pt_len)) {
1755 return SIGIL_UNDEFINED;
1758 mbedtls_gcm_context ctx;
1759 mbedtls_gcm_init(&ctx);
1761 Value result = SIGIL_FALSE;
1762 unsigned char tag_buf[16];
1763 unsigned char *ct_buf = NULL;
1765 int ret = mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key_data, 128);
1766 if (ret != 0) goto cleanup;
1768 ct_buf = (pt_len == 0) ? NULL : malloc(pt_len);
1769 if (pt_len != 0 && !ct_buf) goto cleanup;
1771 ret = mbedtls_gcm_crypt_and_tag(&ctx, MBEDTLS_GCM_ENCRYPT,
1772 pt_len,
1773 iv_data, 12,
1774 aad_data, aad_len,
1775 pt_data, ct_buf,
1776 sizeof(tag_buf), tag_buf);
1777 if (ret != 0) goto cleanup;
1779 Value ct_bv = sigil_make_bytevector(vm, pt_len);
1780 Value tag_bv = sigil_make_bytevector(vm, sizeof(tag_buf));
1781 if (!sigil_is_bytevector(ct_bv) || !sigil_is_bytevector(tag_bv)) {
1782 goto cleanup;
1784 if (pt_len > 0) memcpy(sigil_bytevector_data(ct_bv), ct_buf, pt_len);
1785 memcpy(sigil_bytevector_data(tag_bv), tag_buf, sizeof(tag_buf));
1787 result = sigil_cons(vm, ct_bv, tag_bv);
1789cleanup:
1790 if (ct_buf) free(ct_buf);
1791 mbedtls_gcm_free(&ctx);
1792 return result;
1796 * aes-128-gcm-decrypt key-bv-16 iv-bv-12 aad ciphertext-bv tag-bv-16
1797 * -> plaintext-bv | #f
1799 * Returns #f on auth-tag mismatch (the classic AEAD failure). Used
1800 * by the test path; production WEBPUSH only encrypts.
1801 */
1802static Value native_aes_128_gcm_decrypt(SigilVM *vm, int argc, Value *args)
1804 (void)argc;
1806 const unsigned char *key_data;
1807 if (!crypto_read_bv_exact(vm, args[0], 16, "key", &key_data)) {
1808 return SIGIL_UNDEFINED;
1811 const unsigned char *iv_data;
1812 if (!crypto_read_bv_exact(vm, args[1], 12, "iv", &iv_data)) {
1813 return SIGIL_UNDEFINED;
1816 const unsigned char *aad_data;
1817 size_t aad_len;
1818 if (!crypto_read_bytes(vm, args[2], "aes-128-gcm-decrypt aad",
1819 &aad_data, &aad_len)) {
1820 return SIGIL_UNDEFINED;
1823 if (!sigil_is_bytevector(args[3])) {
1824 sigil__vm_error(vm, SIGIL_ERR_TYPE,
1825 "aes-128-gcm-decrypt: ciphertext must be bytevector");
1826 return SIGIL_UNDEFINED;
1828 SigilBytevector *ct_bv_in = (SigilBytevector *)sigil_as_ptr(args[3]);
1829 const unsigned char *ct_data = ct_bv_in->data;
1830 size_t ct_len = ct_bv_in->length;
1832 const unsigned char *tag_data;
1833 if (!crypto_read_bv_exact(vm, args[4], 16, "tag", &tag_data)) {
1834 return SIGIL_UNDEFINED;
1837 mbedtls_gcm_context ctx;
1838 mbedtls_gcm_init(&ctx);
1840 Value result = SIGIL_FALSE;
1841 unsigned char *pt_buf = NULL;
1843 int ret = mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key_data, 128);
1844 if (ret != 0) goto cleanup;
1846 pt_buf = (ct_len == 0) ? NULL : malloc(ct_len);
1847 if (ct_len != 0 && !pt_buf) goto cleanup;
1849 ret = mbedtls_gcm_auth_decrypt(&ctx, ct_len,
1850 iv_data, 12,
1851 aad_data, aad_len,
1852 tag_data, 16,
1853 ct_data, pt_buf);
1854 if (ret != 0) goto cleanup;
1856 result = sigil_make_bytevector(vm, ct_len);
1857 if (sigil_is_bytevector(result) && ct_len > 0) {
1858 memcpy(sigil_bytevector_data(result), pt_buf, ct_len);
1861cleanup:
1862 if (pt_buf) {
1863 memset(pt_buf, 0, ct_len);
1864 free(pt_buf);
1866 mbedtls_gcm_free(&ctx);
1867 return result;
1872 * Helper macro for module-scoped registration with export
1873 */
1874#define REGISTER_AND_EXPORT(name, func, arity, doc) \
1875 sigil_module_register_native(vm, name, func, arity, doc); \
1876 sigil_module_export(vm, name)
1879 * Initialize the (sigil crypto) module.
1880 * This is called at VM startup.
1881 */
1882void sigil__init_sigil_crypto_module(SigilVM *vm)
1884 SigilModule *module = sigil_begin_module(vm, "(sigil crypto)");
1885 if (!module) return;
1887 /* Hashing */
1888 REGISTER_AND_EXPORT("sha1", native_sha1,
1889 SIGIL_ARITY_EXACT(1), "Compute SHA-1 hash (returns bytevector)");
1890 REGISTER_AND_EXPORT("sha256", native_sha256,
1891 SIGIL_ARITY_EXACT(1), "Compute SHA-256 hash (hex string)");
1892 REGISTER_AND_EXPORT("sha256-bytes", native_sha256_bytes,
1893 SIGIL_ARITY_EXACT(1), "Compute SHA-256 hash (bytevector)");
1894 REGISTER_AND_EXPORT("ripemd160", native_ripemd160,
1895 SIGIL_ARITY_EXACT(1), "Compute RIPEMD-160 hash (returns bytevector)");
1897 /* HMAC */
1898 REGISTER_AND_EXPORT("hmac-sha256", native_hmac_sha256,
1899 SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA256 (hex string)");
1900 REGISTER_AND_EXPORT("hmac-sha256-bytes", native_hmac_sha256_bytes,
1901 SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA256 (bytevector)");
1902 REGISTER_AND_EXPORT("hmac-sha512-bytes", native_hmac_sha512_bytes,
1903 SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA512 (bytevector)");
1904 REGISTER_AND_EXPORT("hmac-sha1", native_hmac_sha1,
1905 SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA1 (returns bytevector)");
1907 /* Key Derivation */
1908 REGISTER_AND_EXPORT("pbkdf2-sha1", native_pbkdf2_sha1,
1909 SIGIL_ARITY_EXACT(4), "Derive key using PBKDF2-HMAC-SHA1");
1910 REGISTER_AND_EXPORT("pbkdf2-sha256", native_pbkdf2_sha256,
1911 SIGIL_ARITY_EXACT(4), "Derive key using PBKDF2-HMAC-SHA256");
1912 REGISTER_AND_EXPORT("pbkdf2-sha512", native_pbkdf2_sha512,
1913 SIGIL_ARITY_EXACT(4), "Derive key using PBKDF2-HMAC-SHA512");
1915 /* Base64 */
1916 REGISTER_AND_EXPORT("base64-encode", native_base64_encode,
1917 SIGIL_ARITY_EXACT(1), "Encode data as base64");
1918 REGISTER_AND_EXPORT("base64-decode", native_base64_decode,
1919 SIGIL_ARITY_EXACT(1), "Decode base64 string");
1921 /* Random */
1922 REGISTER_AND_EXPORT("random-bytes", native_random_bytes,
1923 SIGIL_ARITY_EXACT(1), "Generate secure random bytes");
1925 /* mbedTLS MPI — Big Integer Arithmetic */
1926 REGISTER_AND_EXPORT("mpi-add", native_mpi_add,
1927 SIGIL_ARITY_EXACT(3), "Add two bytevectors as big integers");
1928 REGISTER_AND_EXPORT("mpi-sub", native_mpi_sub,
1929 SIGIL_ARITY_EXACT(3), "Subtract two bytevectors as big integers");
1930 REGISTER_AND_EXPORT("mpi-mul", native_mpi_mul,
1931 SIGIL_ARITY_EXACT(3), "Multiply two bytevectors as big integers");
1932 REGISTER_AND_EXPORT("mpi-div", native_mpi_div,
1933 SIGIL_ARITY_EXACT(3), "Divide two bytevectors: returns (cons quotient remainder)");
1934 REGISTER_AND_EXPORT("mpi-mod", native_mpi_mod,
1935 SIGIL_ARITY_EXACT(3), "Modulo of two bytevectors as big integers");
1936 REGISTER_AND_EXPORT("mpi-mod-add", native_mpi_mod_add,
1937 SIGIL_ARITY_EXACT(4), "(a + b) mod n");
1938 REGISTER_AND_EXPORT("mpi-inv-mod", native_mpi_inv_mod,
1939 SIGIL_ARITY_EXACT(3), "Modular inverse of a modulo n");
1940 REGISTER_AND_EXPORT("mpi-cmp", native_mpi_cmp,
1941 SIGIL_ARITY_EXACT(2), "Compare two bytevectors: returns -1, 0, or 1");
1942 REGISTER_AND_EXPORT("mpi-is-zero?", native_mpi_is_zero,
1943 SIGIL_ARITY_EXACT(1), "Test if bytevectored big integer is zero");
1944 REGISTER_AND_EXPORT("mpi-shift-l", native_mpi_shift_l,
1945 SIGIL_ARITY_EXACT(3), "Left-shift bytevectored big integer");
1946 REGISTER_AND_EXPORT("mpi-shift-r", native_mpi_shift_r,
1947 SIGIL_ARITY_EXACT(2), "Right-shift bytevectored big integer");
1949 /* ECDSA P-256 (VAPID JWT signing) */
1950 REGISTER_AND_EXPORT("ecdsa-p256-generate-keypair",
1951 native_ecdsa_p256_generate_keypair,
1952 SIGIL_ARITY_EXACT(0),
1953 "Generate ECDSA P-256 keypair: returns (cons priv-bv-32 pub-bv-65)");
1954 REGISTER_AND_EXPORT("ecdsa-p256-sign", native_ecdsa_p256_sign,
1955 SIGIL_ARITY_EXACT(2),
1956 "Sign message with ECDSA P-256+SHA-256, JOSE format (r||s, 64 bytes)");
1957 REGISTER_AND_EXPORT("ecdsa-p256-verify", native_ecdsa_p256_verify,
1958 SIGIL_ARITY_EXACT(3),
1959 "Verify ECDSA P-256+SHA-256 signature (JOSE format)");
1961 /* ECDH P-256 (Web Push shared-secret derivation) */
1962 REGISTER_AND_EXPORT("ecdh-p256-shared-secret",
1963 native_ecdh_p256_shared_secret,
1964 SIGIL_ARITY_EXACT(2),
1965 "ECDH P-256: 32-byte X coordinate of priv*peer-pub");
1967 /* AES-128-GCM (Web Push payload envelope) */
1968 REGISTER_AND_EXPORT("aes-128-gcm-encrypt", native_aes_128_gcm_encrypt,
1969 SIGIL_ARITY_EXACT(4),
1970 "AES-128-GCM encrypt: returns (cons ciphertext tag)");
1971 REGISTER_AND_EXPORT("aes-128-gcm-decrypt", native_aes_128_gcm_decrypt,
1972 SIGIL_ARITY_EXACT(5),
1973 "AES-128-GCM decrypt: returns plaintext or #f on auth fail");
1975 /* Ed25519 and BLAKE2b-512 (Monocypher, native/ed25519.c) */
1976 sigil_crypto_register_ed25519(vm);
1978 sigil_end_module(vm);
1981#undef REGISTER_AND_EXPORT
1983#endif /* !(__EMSCRIPTEN__ || __wasi__) */