AtlatestRepositorysigil-crypto
sigil-crypto / tree / nativecrypto.c
1
/*2
* Sigil Crypto Library Implementation3
*4
* This file implements cryptographic operations using mbedTLS:5
* - SHA-256 hashing6
* - HMAC-SHA256 message authentication7
* - Base64 encoding/decoding8
* - 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 a14
* platform entropy source, sockets and a clock that the wasm build does not15
* provide yet (t-8c222f). There the module carries only the Monocypher16
* natives; every Mbed TLS-backed name is registered as a stub that raises17
* "not available on wasm" when called, so a program that uses one fails18
* 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 wasm28
* harness (test/wasm/run-wasm-test.mjs) calls every (sigil crypto) export29
* that is not in its working set and fails if one is missing here. */30
static 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
NULL40
};42
static Value native_unavailable_on_wasm(SigilVM *vm, int argc, Value *args)43
{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;53
}55
void sigil__init_sigil_crypto_module(SigilVM *vm)56
{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);70
}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) */92
static mbedtls_entropy_context entropy_ctx;93
static mbedtls_ctr_drbg_context ctr_drbg_ctx;94
static int rng_initialized = 0;96
/*97
* Initialize the random number generator on first use.98
* Returns 0 on success, non-zero on failure.99
*/100
static int ensure_rng_initialized(void)101
{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;118
}120
/*121
* Helper: Convert bytes to hex string122
*/123
static void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex)124
{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';131
}133
/*134
* sha256 data -> hex-string135
* Compute SHA-256 hash of data (string or bytevector).136
* Returns 64-character hex-encoded hash.137
*/138
static Value native_sha256(SigilVM *vm, int argc, Value *args)139
{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);173
}175
/*176
* sha256-bytes data -> bytevector177
* Compute SHA-256 hash of data (string or bytevector).178
* Returns 32-byte hash as bytevector.179
*/180
static Value native_sha256_bytes(SigilVM *vm, int argc, Value *args)181
{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;216
}218
/*219
* sha1 data -> bytevector220
* 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
*/224
static Value native_sha1(SigilVM *vm, int argc, Value *args)225
{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;260
}262
/*263
* ripemd160 data -> bytevector264
* Compute RIPEMD-160 hash of data (string or bytevector).265
* Returns 20-byte hash as bytevector.266
*/267
static Value native_ripemd160(SigilVM *vm, int argc, Value *args)268
{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;301
}303
/*304
* hmac-sha256 key data -> hex-string305
* 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
*/309
static Value native_hmac_sha256(SigilVM *vm, int argc, Value *args)310
{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);360
}362
/*363
* hmac-sha256-bytes key data -> bytevector364
* Like hmac-sha256 but returns the 32-byte MAC as a bytevector instead365
* of a hex string. Required for SCRAM-SHA-256 where intermediate values366
* are bytewise XORed and concatenated.367
*/368
static Value native_hmac_sha256_bytes(SigilVM *vm, int argc, Value *args)369
{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;416
}418
/*419
* hmac-sha512-bytes key data -> bytevector420
* Like hmac-sha256-bytes but using SHA-512.421
*/422
static Value native_hmac_sha512_bytes(SigilVM *vm, int argc, Value *args)423
{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;470
}472
/*473
* hmac-sha1 key data -> bytevector474
* 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
*/478
static Value native_hmac_sha1(SigilVM *vm, int argc, Value *args)479
{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;528
}530
/*531
* pbkdf2-sha1 password salt iterations key-length -> bytevector532
* Derive a key using PBKDF2 with HMAC-SHA1.533
* Password and salt can be strings or bytevectors.534
* Returns derived key as bytevector.535
*/536
static Value native_pbkdf2_sha1(SigilVM *vm, int argc, Value *args)537
{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;613
}615
/*616
* pbkdf2-sha256 password salt iterations key-length -> bytevector617
* 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 password622
* `Hi(password, salt, iterations)` is PBKDF2-SHA-256 of the user's623
* password against the per-user salt, with iterations chosen by the624
* server (typically 4096+).625
*/626
static Value native_pbkdf2_sha256(SigilVM *vm, int argc, Value *args)627
{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;699
}701
/*702
* pbkdf2-sha512 password salt iterations key-length -> bytevector703
* Derive a key using PBKDF2 with HMAC-SHA512.704
* Password and salt can be strings or bytevectors.705
* Returns derived key as bytevector.706
*/707
static Value native_pbkdf2_sha512(SigilVM *vm, int argc, Value *args)708
{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;780
}782
/*783
* base64-encode data -> string784
* Encode data (string or bytevector) as base64.785
*/786
static Value native_base64_encode(SigilVM *vm, int argc, Value *args)787
{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 added812
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;861
}863
/*864
* base64-decode string -> bytevector | #f865
* Decode base64 string to bytevector.866
* Returns #f if input is not valid base64.867
*/868
static Value native_base64_decode(SigilVM *vm, int argc, Value *args)869
{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 added900
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, ['/'] = 63923
};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;966
}968
/*969
* random-bytes count -> bytevector970
* Generate cryptographically secure random bytes.971
* Uses mbedTLS CTR-DRBG with OS entropy.972
*/973
static Value native_random_bytes(SigilVM *vm, int argc, Value *args)974
{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;1005
}1007
return bv;1008
}1010
/* ===========================================================1011
* mbedTLS MPI — Big Integer Arithmetic1012
*1013
* Exposes mbedtls_mpi for arbitrary-precision integer operations1014
* beyond the 63-bit fixnum range. Bytevectors in big-endian.1015
* Functions allocate mpi contexts internally; no GC-visible handles.1016
* =========================================================== */1018
static int mpi_read_bv(mbedtls_mpi *X, Value bv_val)1019
{1020
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(bv_val);1021
return mbedtls_mpi_read_binary(X, bv->data, bv->length);1022
}1024
static Value mpi_write_bv(SigilVM *vm, mbedtls_mpi *X, size_t size)1025
{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;1031
}1033
/* MPI_BINOP generates add, sub, mul — the only difference is the op. */1034
#define MPI_BINOP(method_name, op_fn) \1035
static 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); \1056
cleanup: \1057
mbedtls_mpi_free(&R); mbedtls_mpi_free(&B); mbedtls_mpi_free(&A); \1058
return result; \1059
}1061
MPI_BINOP(add, mbedtls_mpi_add_mpi)1062
MPI_BINOP(sub, mbedtls_mpi_sub_mpi)1063
MPI_BINOP(mul, mbedtls_mpi_mul_mpi)1065
#undef MPI_BINOP1067
/*1068
* mpi-div a-bv b-bv size -> (cons quotient-bv remainder-bv)1069
*/1070
static Value native_mpi_div(SigilVM *vm, int argc, Value *args)1071
{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;1077
}1078
if (!sigil_is_fixnum(args[2])) {1079
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-div: expected integer size");1080
return SIGIL_UNDEFINED;1081
}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);1101
cleanup:1102
mbedtls_mpi_free(&R); mbedtls_mpi_free(&Q);1103
mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);1104
return result;1105
}1107
/*1108
* mpi-mod a-bv n-bv size -> bytevector1109
*/1110
static Value native_mpi_mod(SigilVM *vm, int argc, Value *args)1111
{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;1117
}1118
if (!sigil_is_fixnum(args[2])) {1119
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod: expected integer size");1120
return SIGIL_UNDEFINED;1121
}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);1136
cleanup:1137
mbedtls_mpi_free(&R); mbedtls_mpi_free(&N); mbedtls_mpi_free(&A);1138
return result;1139
}1141
/*1142
* mpi-mod-add a-bv b-bv n-bv size -> bytevector1143
* (a + b) mod n1144
*/1145
static Value native_mpi_mod_add(SigilVM *vm, int argc, Value *args)1146
{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;1153
}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;1157
}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);1175
cleanup:1176
mbedtls_mpi_free(&R); mbedtls_mpi_free(&N);1177
mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);1178
return result;1179
}1181
/*1182
* mpi-inv-mod a-bv n-bv size -> bytevector | #f1183
*/1184
static Value native_mpi_inv_mod(SigilVM *vm, int argc, Value *args)1185
{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;1191
}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;1195
}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);1210
cleanup:1211
mbedtls_mpi_free(&R); mbedtls_mpi_free(&N); mbedtls_mpi_free(&A);1212
return result;1213
}1215
/*1216
* mpi-cmp a-bv b-bv -> fixnum (-1, 0, or 1)1217
*/1218
static Value native_mpi_cmp(SigilVM *vm, int argc, Value *args)1219
{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;1225
}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);1237
cleanup:1238
mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);1239
return SIGIL_FALSE;1240
}1242
/*1243
* mpi-is-zero? a-bv -> boolean1244
*/1245
static Value native_mpi_is_zero(SigilVM *vm, int argc, Value *args)1246
{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;1252
}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;1260
}1262
int result = (mbedtls_mpi_cmp_int(&A, 0) == 0);1263
mbedtls_mpi_free(&A);1264
return result ? SIGIL_TRUE : SIGIL_FALSE;1265
}1267
/*1268
* mpi-shift-l a-bv bits size -> bytevector1269
*/1270
static Value native_mpi_shift_l(SigilVM *vm, int argc, Value *args)1271
{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;1277
}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;1281
}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;1289
}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;1300
}1302
if (mbedtls_mpi_shift_l(&A, bits) != 0) {1303
mbedtls_mpi_free(&A);1304
return SIGIL_FALSE;1305
}1307
Value result = mpi_write_bv(vm, &A, size);1308
mbedtls_mpi_free(&A);1309
return result;1310
}1312
/*1313
* mpi-shift-r a-bv bits -> bytevector1314
*/1315
static Value native_mpi_shift_r(SigilVM *vm, int argc, Value *args)1316
{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;1322
}1323
if (!sigil_is_fixnum(args[1])) {1324
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-r: expected integer");1325
return SIGIL_UNDEFINED;1326
}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;1333
}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;1343
}1345
if (mbedtls_mpi_shift_r(&A, bits) != 0) {1346
mbedtls_mpi_free(&A);1347
return SIGIL_FALSE;1348
}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;1354
}1356
/* ===========================================================1357
* ECDSA P-256, ECDH P-256, AES-128-GCM1358
*1359
* Used by Web Push (RFC 8291 / RFC 8292): VAPID JWT signs with1360
* ES256 (ECDSA P-256 + SHA-256), payload encryption derives1361
* shared secret via ECDH P-256 and seals with AES-128-GCM.1362
* =========================================================== */1364
#define ECDSA_P256_PRIV_LEN 321365
#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 321369
/*1370
* Extract bytes from a string-or-bytevector argument.1371
* On type mismatch raises a VM error and returns 0.1372
*/1373
static int crypto_read_bytes(SigilVM *vm, Value v, const char *fn,1374
const unsigned char **out_data, size_t *out_len)1375
{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;1381
}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;1387
}1388
sigil__vm_error(vm, SIGIL_ERR_TYPE,1389
"expected string or bytevector argument");1390
(void)fn;1391
return 0;1392
}1394
/*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
*/1398
static int crypto_read_bv_exact(SigilVM *vm, Value v, size_t want,1399
const char *what,1400
const unsigned char **out_data)1401
{1402
if (!sigil_is_bytevector(v)) {1403
sigil__vm_error(vm, SIGIL_ERR_TYPE,1404
"expected bytevector argument");1405
(void)what;1406
return 0;1407
}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;1413
}1414
*out_data = bv->data;1415
return 1;1416
}1418
/*1419
* ecdsa-p256-generate-keypair -> (cons priv-bv-32 pub-bv-65)1420
*1421
* Generates a fresh P-256 keypair. priv is the 32-byte big-endian1422
* scalar; pub is the 65-byte uncompressed-point encoding suitable1423
* for VAPID's `applicationServerKey` and for ECDH peer-key input.1424
*/1425
static Value native_ecdsa_p256_generate_keypair(SigilVM *vm, int argc, Value *args)1426
{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;1433
}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;1468
}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);1474
cleanup: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;1481
}1483
/*1484
* ecdsa-p256-sign priv-bv message -> sig-bv-64 | #f1485
*1486
* Hashes `message` with SHA-256, signs with ECDSA P-256 using the1487
* provided 32-byte private scalar, and returns the JOSE-format1488
* 64-byte signature (r || s, each 32 bytes big-endian). This is1489
* the format VAPID JWT (ES256) wants — NOT DER. Returns #f if1490
* the private key is invalid.1491
*/1492
static Value native_ecdsa_p256_sign(SigilVM *vm, int argc, Value *args)1493
{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;1500
}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;1507
}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;1513
}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 validate1542
* 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);1558
}1560
cleanup:1561
mbedtls_mpi_free(&s);1562
mbedtls_mpi_free(&r);1563
mbedtls_mpi_free(&d);1564
mbedtls_ecp_group_free(&grp);1565
return result;1566
}1568
/*1569
* ecdsa-p256-verify pub-bv message sig-bv -> boolean1570
*1571
* Returns #t when the JOSE-format 64-byte sig validates against1572
* the message under the given 65-byte uncompressed-point public key,1573
* otherwise #f. Hashes the message with SHA-256 internally so the1574
* caller passes the raw message body (matches sign's input shape).1575
*/1576
static Value native_ecdsa_p256_verify(SigilVM *vm, int argc, Value *args)1577
{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;1584
}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;1591
}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;1597
}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-curve1625
* 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;1636
cleanup: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;1642
}1644
/*1645
* ecdh-p256-shared-secret priv-bv peer-pub-bv -> bytevector(32) | #f1646
*1647
* ECDH on P-256: derives the 32-byte big-endian X coordinate of1648
* (priv * peer_pub). The shared secret is the raw X coordinate per1649
* 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 before1651
* computing — invalid-curve attack defence.1652
*/1653
static Value native_ecdh_p256_shared_secret(SigilVM *vm, int argc, Value *args)1654
{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;1661
}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;1667
}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;1673
}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);1710
}1712
cleanup: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;1719
}1721
/*1722
* aes-128-gcm-encrypt key-bv-16 iv-bv-12 aad plaintext1723
* -> (cons ciphertext-bv tag-bv-16) | #f1724
*1725
* AAD and plaintext accept string or bytevector. Ciphertext length1726
* matches plaintext length; tag is always 16 bytes (full GCM tag).1727
* IV must be 12 bytes (the AEAD-recommended length, and what1728
* RFC 8291 § 3 prescribes).1729
*/1730
static Value native_aes_128_gcm_encrypt(SigilVM *vm, int argc, Value *args)1731
{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;1737
}1739
const unsigned char *iv_data;1740
if (!crypto_read_bv_exact(vm, args[1], 12, "iv", &iv_data)) {1741
return SIGIL_UNDEFINED;1742
}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;1749
}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;1756
}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;1783
}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);1789
cleanup:1790
if (ct_buf) free(ct_buf);1791
mbedtls_gcm_free(&ctx);1792
return result;1793
}1795
/*1796
* aes-128-gcm-decrypt key-bv-16 iv-bv-12 aad ciphertext-bv tag-bv-161797
* -> plaintext-bv | #f1798
*1799
* Returns #f on auth-tag mismatch (the classic AEAD failure). Used1800
* by the test path; production WEBPUSH only encrypts.1801
*/1802
static Value native_aes_128_gcm_decrypt(SigilVM *vm, int argc, Value *args)1803
{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;1809
}1811
const unsigned char *iv_data;1812
if (!crypto_read_bv_exact(vm, args[1], 12, "iv", &iv_data)) {1813
return SIGIL_UNDEFINED;1814
}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;1821
}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;1827
}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;1835
}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);1859
}1861
cleanup:1862
if (pt_buf) {1863
memset(pt_buf, 0, ct_len);1864
free(pt_buf);1865
}1866
mbedtls_gcm_free(&ctx);1867
return result;1868
}1871
/*1872
* Helper macro for module-scoped registration with export1873
*/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)1878
/*1879
* Initialize the (sigil crypto) module.1880
* This is called at VM startup.1881
*/1882
void sigil__init_sigil_crypto_module(SigilVM *vm)1883
{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);1979
}1981
#undef REGISTER_AND_EXPORT1983
#endif /* !(__EMSCRIPTEN__ || __wasi__) */