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
* Note: Crypto is not available on Emscripten/WebAssembly builds.11
* Web applications should use the Web Crypto API via JavaScript interop.12
*/14
#include <sigil/sigil.h>16
#ifdef __EMSCRIPTEN__17
/*18
* Stub implementation for Emscripten builds.19
* Crypto operations should use Web Crypto API in the browser.20
*/21
void sigil__init_sigil_crypto_module(SigilVM *vm)22
{23
(void)vm;24
/* Crypto module not available on web platform */25
}27
#else /* Native build */28
#include <stdio.h>29
#include <stdlib.h>30
#include <string.h>32
#include "mbedtls/sha1.h"33
#include "mbedtls/sha256.h"34
#include "mbedtls/ripemd160.h"35
#include "mbedtls/md.h"36
#include "mbedtls/base64.h"37
#include "mbedtls/entropy.h"38
#include "mbedtls/ctr_drbg.h"39
#include "mbedtls/pkcs5.h"40
#include "mbedtls/ecp.h"41
#include "mbedtls/ecdsa.h"42
#include "mbedtls/ecdh.h"43
#include "mbedtls/bignum.h"44
#include "mbedtls/gcm.h"46
/* Global RNG context (initialized on first use) */47
static mbedtls_entropy_context entropy_ctx;48
static mbedtls_ctr_drbg_context ctr_drbg_ctx;49
static int rng_initialized = 0;51
/*52
* Initialize the random number generator on first use.53
* Returns 0 on success, non-zero on failure.54
*/55
static int ensure_rng_initialized(void)56
{57
if (rng_initialized) return 0;59
mbedtls_entropy_init(&entropy_ctx);60
mbedtls_ctr_drbg_init(&ctr_drbg_ctx);62
/* Seed the DRBG with entropy from the OS */63
int ret = mbedtls_ctr_drbg_seed(&ctr_drbg_ctx, mbedtls_entropy_func,64
&entropy_ctx, NULL, 0);65
if (ret != 0) {66
mbedtls_ctr_drbg_free(&ctr_drbg_ctx);67
mbedtls_entropy_free(&entropy_ctx);68
return ret;69
}71
rng_initialized = 1;72
return 0;73
}75
/*76
* Helper: Convert bytes to hex string77
*/78
static void bytes_to_hex(const unsigned char *bytes, size_t len, char *hex)79
{80
static const char hex_chars[] = "0123456789abcdef";81
for (size_t i = 0; i < len; i++) {82
hex[i * 2] = hex_chars[(bytes[i] >> 4) & 0x0f];83
hex[i * 2 + 1] = hex_chars[bytes[i] & 0x0f];84
}85
hex[len * 2] = '\0';86
}88
/*89
* sha256 data -> hex-string90
* Compute SHA-256 hash of data (string or bytevector).91
* Returns 64-character hex-encoded hash.92
*/93
static Value native_sha256(SigilVM *vm, int argc, Value *args)94
{95
(void)argc;97
const unsigned char *data;98
size_t len;100
if (sigil_is_string(args[0])) {101
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);102
data = (const unsigned char *)s->data;103
len = s->byte_length;104
} else if (sigil_is_bytevector(args[0])) {105
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);106
data = bv->data;107
len = bv->length;108
} else {109
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sha256: expected string or bytevector");110
return SIGIL_UNDEFINED;111
}113
unsigned char hash[32]; /* SHA-256 produces 32 bytes */115
/* Use mbedTLS SHA-256 */116
mbedtls_sha256_context ctx;117
mbedtls_sha256_init(&ctx);118
mbedtls_sha256_starts(&ctx, 0); /* 0 = SHA-256, 1 = SHA-224 */119
mbedtls_sha256_update(&ctx, data, len);120
mbedtls_sha256_finish(&ctx, hash);121
mbedtls_sha256_free(&ctx);123
/* Convert to hex string */124
char hex[65];125
bytes_to_hex(hash, 32, hex);127
return sigil_make_string(vm, hex, 64);128
}130
/*131
* sha256-bytes data -> bytevector132
* Compute SHA-256 hash of data (string or bytevector).133
* Returns 32-byte hash as bytevector.134
*/135
static Value native_sha256_bytes(SigilVM *vm, int argc, Value *args)136
{137
(void)argc;139
const unsigned char *data;140
size_t len;142
if (sigil_is_string(args[0])) {143
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);144
data = (const unsigned char *)s->data;145
len = s->byte_length;146
} else if (sigil_is_bytevector(args[0])) {147
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);148
data = bv->data;149
len = bv->length;150
} else {151
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sha256-bytes: expected string or bytevector");152
return SIGIL_UNDEFINED;153
}155
unsigned char hash[32]; /* SHA-256 produces 32 bytes */157
/* Use mbedTLS SHA-256 */158
mbedtls_sha256_context ctx;159
mbedtls_sha256_init(&ctx);160
mbedtls_sha256_starts(&ctx, 0); /* 0 = SHA-256, 1 = SHA-224 */161
mbedtls_sha256_update(&ctx, data, len);162
mbedtls_sha256_finish(&ctx, hash);163
mbedtls_sha256_free(&ctx);165
/* Return as bytevector */166
Value result = sigil_make_bytevector(vm, 32);167
if (sigil_is_bytevector(result)) {168
memcpy(sigil_bytevector_data(result), hash, 32);169
}170
return result;171
}173
/*174
* sha1 data -> bytevector175
* Compute SHA-1 hash of data (string or bytevector).176
* Returns 20-byte hash as bytevector (for WebSocket handshake compatibility).177
* Note: SHA-1 is cryptographically weak; use SHA-256 for new applications.178
*/179
static Value native_sha1(SigilVM *vm, int argc, Value *args)180
{181
(void)argc;183
const unsigned char *data;184
size_t len;186
if (sigil_is_string(args[0])) {187
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);188
data = (const unsigned char *)s->data;189
len = s->byte_length;190
} else if (sigil_is_bytevector(args[0])) {191
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);192
data = bv->data;193
len = bv->length;194
} else {195
sigil__vm_error(vm, SIGIL_ERR_TYPE, "sha1: expected string or bytevector");196
return SIGIL_UNDEFINED;197
}199
unsigned char hash[20]; /* SHA-1 produces 20 bytes */201
/* Use mbedTLS SHA-1 */202
mbedtls_sha1_context ctx;203
mbedtls_sha1_init(&ctx);204
mbedtls_sha1_starts(&ctx);205
mbedtls_sha1_update(&ctx, data, len);206
mbedtls_sha1_finish(&ctx, hash);207
mbedtls_sha1_free(&ctx);209
/* Return as bytevector for use with base64-encode */210
Value result = sigil_make_bytevector(vm, 20);211
if (sigil_is_bytevector(result)) {212
memcpy(sigil_bytevector_data(result), hash, 20);213
}214
return result;215
}217
/*218
* ripemd160 data -> bytevector219
* Compute RIPEMD-160 hash of data (string or bytevector).220
* Returns 20-byte hash as bytevector.221
*/222
static Value native_ripemd160(SigilVM *vm, int argc, Value *args)223
{224
(void)argc;226
const unsigned char *data;227
size_t len;229
if (sigil_is_string(args[0])) {230
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);231
data = (const unsigned char *)s->data;232
len = s->byte_length;233
} else if (sigil_is_bytevector(args[0])) {234
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);235
data = bv->data;236
len = bv->length;237
} else {238
sigil__vm_error(vm, SIGIL_ERR_TYPE, "ripemd160: expected string or bytevector");239
return SIGIL_UNDEFINED;240
}242
unsigned char hash[20];244
mbedtls_ripemd160_context ctx;245
mbedtls_ripemd160_init(&ctx);246
mbedtls_ripemd160_starts(&ctx);247
mbedtls_ripemd160_update(&ctx, data, len);248
mbedtls_ripemd160_finish(&ctx, hash);249
mbedtls_ripemd160_free(&ctx);251
Value result = sigil_make_bytevector(vm, 20);252
if (sigil_is_bytevector(result)) {253
memcpy(sigil_bytevector_data(result), hash, 20);254
}255
return result;256
}258
/*259
* hmac-sha256 key data -> hex-string260
* Compute HMAC-SHA256 of data using the given key.261
* Both key and data can be strings or bytevectors.262
* Returns 64-character hex-encoded MAC.263
*/264
static Value native_hmac_sha256(SigilVM *vm, int argc, Value *args)265
{266
(void)argc;268
const unsigned char *key_data;269
size_t key_len;270
const unsigned char *msg_data;271
size_t msg_len;273
/* Get key */274
if (sigil_is_string(args[0])) {275
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);276
key_data = (const unsigned char *)s->data;277
key_len = s->byte_length;278
} else if (sigil_is_bytevector(args[0])) {279
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);280
key_data = bv->data;281
key_len = bv->length;282
} else {283
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256: expected string or bytevector for key");284
return SIGIL_UNDEFINED;285
}287
/* Get message */288
if (sigil_is_string(args[1])) {289
SigilString *s = (SigilString *)sigil_as_ptr(args[1]);290
msg_data = (const unsigned char *)s->data;291
msg_len = s->byte_length;292
} else if (sigil_is_bytevector(args[1])) {293
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);294
msg_data = bv->data;295
msg_len = bv->length;296
} else {297
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256: expected string or bytevector for data");298
return SIGIL_UNDEFINED;299
}301
unsigned char hmac[32]; /* HMAC-SHA256 produces 32 bytes */303
/* Use mbedTLS HMAC */304
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);305
int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);306
if (ret != 0) {307
return SIGIL_FALSE;308
}310
/* Convert to hex string */311
char hex[65];312
bytes_to_hex(hmac, 32, hex);314
return sigil_make_string(vm, hex, 64);315
}317
/*318
* hmac-sha256-bytes key data -> bytevector319
* Like hmac-sha256 but returns the 32-byte MAC as a bytevector instead320
* of a hex string. Required for SCRAM-SHA-256 where intermediate values321
* are bytewise XORed and concatenated.322
*/323
static Value native_hmac_sha256_bytes(SigilVM *vm, int argc, Value *args)324
{325
(void)argc;327
const unsigned char *key_data;328
size_t key_len;329
const unsigned char *msg_data;330
size_t msg_len;332
if (sigil_is_string(args[0])) {333
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);334
key_data = (const unsigned char *)s->data;335
key_len = s->byte_length;336
} else if (sigil_is_bytevector(args[0])) {337
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);338
key_data = bv->data;339
key_len = bv->length;340
} else {341
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256-bytes: expected string or bytevector for key");342
return SIGIL_UNDEFINED;343
}345
if (sigil_is_string(args[1])) {346
SigilString *s = (SigilString *)sigil_as_ptr(args[1]);347
msg_data = (const unsigned char *)s->data;348
msg_len = s->byte_length;349
} else if (sigil_is_bytevector(args[1])) {350
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);351
msg_data = bv->data;352
msg_len = bv->length;353
} else {354
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha256-bytes: expected string or bytevector for data");355
return SIGIL_UNDEFINED;356
}358
unsigned char hmac[32];360
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);361
int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);362
if (ret != 0) {363
return SIGIL_FALSE;364
}366
Value result = sigil_make_bytevector(vm, 32);367
if (sigil_is_bytevector(result)) {368
memcpy(sigil_bytevector_data(result), hmac, 32);369
}370
return result;371
}373
/*374
* hmac-sha512-bytes key data -> bytevector375
* Like hmac-sha256-bytes but using SHA-512.376
*/377
static Value native_hmac_sha512_bytes(SigilVM *vm, int argc, Value *args)378
{379
(void)argc;381
const unsigned char *key_data;382
size_t key_len;383
const unsigned char *msg_data;384
size_t msg_len;386
if (sigil_is_string(args[0])) {387
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);388
key_data = (const unsigned char *)s->data;389
key_len = s->byte_length;390
} else if (sigil_is_bytevector(args[0])) {391
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);392
key_data = bv->data;393
key_len = bv->length;394
} else {395
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha512-bytes: expected string or bytevector for key");396
return SIGIL_UNDEFINED;397
}399
if (sigil_is_string(args[1])) {400
SigilString *s = (SigilString *)sigil_as_ptr(args[1]);401
msg_data = (const unsigned char *)s->data;402
msg_len = s->byte_length;403
} else if (sigil_is_bytevector(args[1])) {404
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);405
msg_data = bv->data;406
msg_len = bv->length;407
} else {408
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha512-bytes: expected string or bytevector for data");409
return SIGIL_UNDEFINED;410
}412
unsigned char hmac[64];414
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);415
int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);416
if (ret != 0) {417
return SIGIL_FALSE;418
}420
Value result = sigil_make_bytevector(vm, 64);421
if (sigil_is_bytevector(result)) {422
memcpy(sigil_bytevector_data(result), hmac, 64);423
}424
return result;425
}427
/*428
* hmac-sha1 key data -> bytevector429
* Compute HMAC-SHA1 of data using the given key.430
* Both key and data can be strings or bytevectors.431
* Returns 20-byte MAC as bytevector.432
*/433
static Value native_hmac_sha1(SigilVM *vm, int argc, Value *args)434
{435
(void)argc;437
const unsigned char *key_data;438
size_t key_len;439
const unsigned char *msg_data;440
size_t msg_len;442
/* Get key */443
if (sigil_is_string(args[0])) {444
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);445
key_data = (const unsigned char *)s->data;446
key_len = s->byte_length;447
} else if (sigil_is_bytevector(args[0])) {448
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);449
key_data = bv->data;450
key_len = bv->length;451
} else {452
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha1: expected string or bytevector for key");453
return SIGIL_UNDEFINED;454
}456
/* Get message */457
if (sigil_is_string(args[1])) {458
SigilString *s = (SigilString *)sigil_as_ptr(args[1]);459
msg_data = (const unsigned char *)s->data;460
msg_len = s->byte_length;461
} else if (sigil_is_bytevector(args[1])) {462
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);463
msg_data = bv->data;464
msg_len = bv->length;465
} else {466
sigil__vm_error(vm, SIGIL_ERR_TYPE, "hmac-sha1: expected string or bytevector for data");467
return SIGIL_UNDEFINED;468
}470
unsigned char hmac[20]; /* HMAC-SHA1 produces 20 bytes */472
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1);473
int ret = mbedtls_md_hmac(md_info, key_data, key_len, msg_data, msg_len, hmac);474
if (ret != 0) {475
return SIGIL_FALSE;476
}478
Value result = sigil_make_bytevector(vm, 20);479
if (sigil_is_bytevector(result)) {480
memcpy(sigil_bytevector_data(result), hmac, 20);481
}482
return result;483
}485
/*486
* pbkdf2-sha1 password salt iterations key-length -> bytevector487
* Derive a key using PBKDF2 with HMAC-SHA1.488
* Password and salt can be strings or bytevectors.489
* Returns derived key as bytevector.490
*/491
static Value native_pbkdf2_sha1(SigilVM *vm, int argc, Value *args)492
{493
(void)argc;495
const unsigned char *password;496
size_t password_len;497
const unsigned char *salt;498
size_t salt_len;500
/* Get password */501
if (sigil_is_string(args[0])) {502
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);503
password = (const unsigned char *)s->data;504
password_len = s->byte_length;505
} else if (sigil_is_bytevector(args[0])) {506
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);507
password = bv->data;508
password_len = bv->length;509
} else {510
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected string or bytevector for password");511
return SIGIL_UNDEFINED;512
}514
/* Get salt */515
if (sigil_is_string(args[1])) {516
SigilString *s = (SigilString *)sigil_as_ptr(args[1]);517
salt = (const unsigned char *)s->data;518
salt_len = s->byte_length;519
} else if (sigil_is_bytevector(args[1])) {520
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);521
salt = bv->data;522
salt_len = bv->length;523
} else {524
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected string or bytevector for salt");525
return SIGIL_UNDEFINED;526
}528
/* Get iterations */529
if (!sigil_is_fixnum(args[2])) {530
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected integer for iterations");531
return SIGIL_UNDEFINED;532
}533
int iterations = (int)sigil_as_fixnum(args[2]);534
if (iterations < 1) {535
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha1: iterations must be positive");536
return SIGIL_UNDEFINED;537
}539
/* Get key length */540
if (!sigil_is_fixnum(args[3])) {541
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha1: expected integer for key-length");542
return SIGIL_UNDEFINED;543
}544
int key_length = (int)sigil_as_fixnum(args[3]);545
if (key_length < 1 || key_length > 65536) {546
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha1: key-length must be 1-65536");547
return SIGIL_UNDEFINED;548
}550
unsigned char *output = malloc(key_length);551
if (!output) return SIGIL_FALSE;553
int ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA1,554
password, password_len,555
salt, salt_len,556
iterations, key_length, output);557
if (ret != 0) {558
free(output);559
return SIGIL_FALSE;560
}562
Value result = sigil_make_bytevector(vm, key_length);563
if (sigil_is_bytevector(result)) {564
memcpy(sigil_bytevector_data(result), output, key_length);565
}566
free(output);567
return result;568
}570
/*571
* pbkdf2-sha256 password salt iterations key-length -> bytevector572
* Derive a key using PBKDF2 with HMAC-SHA256.573
* Password and salt can be strings or bytevectors.574
* Returns derived key as bytevector.575
*576
* Required for SCRAM-SHA-256 (RFC 5802 / RFC 7677): the salted password577
* `Hi(password, salt, iterations)` is PBKDF2-SHA-256 of the user's578
* password against the per-user salt, with iterations chosen by the579
* server (typically 4096+).580
*/581
static Value native_pbkdf2_sha256(SigilVM *vm, int argc, Value *args)582
{583
(void)argc;585
const unsigned char *password;586
size_t password_len;587
const unsigned char *salt;588
size_t salt_len;590
if (sigil_is_string(args[0])) {591
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);592
password = (const unsigned char *)s->data;593
password_len = s->byte_length;594
} else if (sigil_is_bytevector(args[0])) {595
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);596
password = bv->data;597
password_len = bv->length;598
} else {599
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected string or bytevector for password");600
return SIGIL_UNDEFINED;601
}603
if (sigil_is_string(args[1])) {604
SigilString *s = (SigilString *)sigil_as_ptr(args[1]);605
salt = (const unsigned char *)s->data;606
salt_len = s->byte_length;607
} else if (sigil_is_bytevector(args[1])) {608
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);609
salt = bv->data;610
salt_len = bv->length;611
} else {612
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected string or bytevector for salt");613
return SIGIL_UNDEFINED;614
}616
if (!sigil_is_fixnum(args[2])) {617
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected integer for iterations");618
return SIGIL_UNDEFINED;619
}620
int iterations = (int)sigil_as_fixnum(args[2]);621
if (iterations < 1) {622
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha256: iterations must be positive");623
return SIGIL_UNDEFINED;624
}626
if (!sigil_is_fixnum(args[3])) {627
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha256: expected integer for key-length");628
return SIGIL_UNDEFINED;629
}630
int key_length = (int)sigil_as_fixnum(args[3]);631
if (key_length < 1 || key_length > 65536) {632
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha256: key-length must be 1-65536");633
return SIGIL_UNDEFINED;634
}636
unsigned char *output = malloc(key_length);637
if (!output) return SIGIL_FALSE;639
int ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA256,640
password, password_len,641
salt, salt_len,642
iterations, key_length, output);643
if (ret != 0) {644
free(output);645
return SIGIL_FALSE;646
}648
Value result = sigil_make_bytevector(vm, key_length);649
if (sigil_is_bytevector(result)) {650
memcpy(sigil_bytevector_data(result), output, key_length);651
}652
free(output);653
return result;654
}656
/*657
* pbkdf2-sha512 password salt iterations key-length -> bytevector658
* Derive a key using PBKDF2 with HMAC-SHA512.659
* Password and salt can be strings or bytevectors.660
* Returns derived key as bytevector.661
*/662
static Value native_pbkdf2_sha512(SigilVM *vm, int argc, Value *args)663
{664
(void)argc;666
const unsigned char *password;667
size_t password_len;668
const unsigned char *salt;669
size_t salt_len;671
if (sigil_is_string(args[0])) {672
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);673
password = (const unsigned char *)s->data;674
password_len = s->byte_length;675
} else if (sigil_is_bytevector(args[0])) {676
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);677
password = bv->data;678
password_len = bv->length;679
} else {680
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected string or bytevector for password");681
return SIGIL_UNDEFINED;682
}684
if (sigil_is_string(args[1])) {685
SigilString *s = (SigilString *)sigil_as_ptr(args[1]);686
salt = (const unsigned char *)s->data;687
salt_len = s->byte_length;688
} else if (sigil_is_bytevector(args[1])) {689
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[1]);690
salt = bv->data;691
salt_len = bv->length;692
} else {693
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected string or bytevector for salt");694
return SIGIL_UNDEFINED;695
}697
if (!sigil_is_fixnum(args[2])) {698
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected integer for iterations");699
return SIGIL_UNDEFINED;700
}701
int iterations = (int)sigil_as_fixnum(args[2]);702
if (iterations < 1) {703
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha512: iterations must be positive");704
return SIGIL_UNDEFINED;705
}707
if (!sigil_is_fixnum(args[3])) {708
sigil__vm_error(vm, SIGIL_ERR_TYPE, "pbkdf2-sha512: expected integer for key-length");709
return SIGIL_UNDEFINED;710
}711
int key_length = (int)sigil_as_fixnum(args[3]);712
if (key_length < 1 || key_length > 65536) {713
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "pbkdf2-sha512: key-length must be 1-65536");714
return SIGIL_UNDEFINED;715
}717
unsigned char *output = malloc(key_length);718
if (!output) return SIGIL_FALSE;720
int ret = mbedtls_pkcs5_pbkdf2_hmac_ext(MBEDTLS_MD_SHA512,721
password, password_len,722
salt, salt_len,723
iterations, key_length, output);724
if (ret != 0) {725
free(output);726
return SIGIL_FALSE;727
}729
Value result = sigil_make_bytevector(vm, key_length);730
if (sigil_is_bytevector(result)) {731
memcpy(sigil_bytevector_data(result), output, key_length);732
}733
free(output);734
return result;735
}737
/*738
* base64-encode data -> string739
* Encode data (string or bytevector) as base64.740
*/741
static Value native_base64_encode(SigilVM *vm, int argc, Value *args)742
{743
(void)argc;745
const unsigned char *data;746
size_t len;748
if (sigil_is_string(args[0])) {749
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);750
data = (const unsigned char *)s->data;751
len = s->byte_length;752
} else if (sigil_is_bytevector(args[0])) {753
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(args[0]);754
data = bv->data;755
len = bv->length;756
} else {757
sigil__vm_error(vm, SIGIL_ERR_TYPE, "base64-encode: expected string or bytevector");758
return SIGIL_UNDEFINED;759
}761
/* Calculate output size: 4 * ceil(len/3) + 1 for null terminator */762
size_t out_len = ((len + 2) / 3) * 4 + 1;763
char *output = malloc(out_len);764
if (!output) return SIGIL_FALSE;766
/* TODO: Implement when mbedTLS is added767
size_t olen;768
int ret = mbedtls_base64_encode((unsigned char *)output, out_len, &olen, data, len);769
if (ret != 0) {770
free(output);771
return SIGIL_FALSE;772
}773
Value result = sigil_make_string(vm, output, olen);774
*/776
/* Temporary stub: use simple base64 implementation */777
static const char base64_chars[] =778
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";780
size_t i = 0, j = 0;781
unsigned char array3[3], array4[4];783
while (len--) {784
array3[i++] = *(data++);785
if (i == 3) {786
array4[0] = (array3[0] & 0xfc) >> 2;787
array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);788
array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);789
array4[3] = array3[2] & 0x3f;791
for (i = 0; i < 4; i++)792
output[j++] = base64_chars[array4[i]];793
i = 0;794
}795
}797
if (i) {798
for (size_t k = i; k < 3; k++)799
array3[k] = '\0';801
array4[0] = (array3[0] & 0xfc) >> 2;802
array4[1] = ((array3[0] & 0x03) << 4) + ((array3[1] & 0xf0) >> 4);803
array4[2] = ((array3[1] & 0x0f) << 2) + ((array3[2] & 0xc0) >> 6);805
for (size_t k = 0; k < i + 1; k++)806
output[j++] = base64_chars[array4[k]];808
while (i++ < 3)809
output[j++] = '=';810
}812
output[j] = '\0';813
Value result = sigil_make_string(vm, output, j);814
free(output);815
return result;816
}818
/*819
* base64-decode string -> bytevector | #f820
* Decode base64 string to bytevector.821
* Returns #f if input is not valid base64.822
*/823
static Value native_base64_decode(SigilVM *vm, int argc, Value *args)824
{825
(void)argc;827
if (!sigil_is_string(args[0])) {828
sigil__vm_error(vm, SIGIL_ERR_TYPE, "base64-decode: expected string");829
return SIGIL_UNDEFINED;830
}832
SigilString *s = (SigilString *)sigil_as_ptr(args[0]);833
const char *data = s->data;834
size_t len = s->byte_length;836
/* Skip trailing whitespace */837
while (len > 0 && (data[len-1] == ' ' || data[len-1] == '\n' ||838
data[len-1] == '\r' || data[len-1] == '\t')) {839
len--;840
}842
if (len == 0) {843
return sigil_make_bytevector(vm, 0);844
}846
/* Calculate output size: 3 * (len/4) */847
size_t out_len = (len / 4) * 3;848
if (len > 0 && data[len-1] == '=') out_len--;849
if (len > 1 && data[len-2] == '=') out_len--;851
unsigned char *output = malloc(out_len + 1);852
if (!output) return SIGIL_FALSE;854
/* TODO: Implement when mbedTLS is added855
size_t olen;856
int ret = mbedtls_base64_decode(output, out_len + 1, &olen,857
(const unsigned char *)data, len);858
if (ret != 0) {859
free(output);860
return SIGIL_FALSE;861
}862
Value result = sigil_make_bytevector(vm, output, olen);863
*/865
/* Simple base64 decode implementation */866
static const unsigned char base64_table[256] = {867
['A'] = 0, ['B'] = 1, ['C'] = 2, ['D'] = 3, ['E'] = 4, ['F'] = 5,868
['G'] = 6, ['H'] = 7, ['I'] = 8, ['J'] = 9, ['K'] = 10, ['L'] = 11,869
['M'] = 12, ['N'] = 13, ['O'] = 14, ['P'] = 15, ['Q'] = 16, ['R'] = 17,870
['S'] = 18, ['T'] = 19, ['U'] = 20, ['V'] = 21, ['W'] = 22, ['X'] = 23,871
['Y'] = 24, ['Z'] = 25, ['a'] = 26, ['b'] = 27, ['c'] = 28, ['d'] = 29,872
['e'] = 30, ['f'] = 31, ['g'] = 32, ['h'] = 33, ['i'] = 34, ['j'] = 35,873
['k'] = 36, ['l'] = 37, ['m'] = 38, ['n'] = 39, ['o'] = 40, ['p'] = 41,874
['q'] = 42, ['r'] = 43, ['s'] = 44, ['t'] = 45, ['u'] = 46, ['v'] = 47,875
['w'] = 48, ['x'] = 49, ['y'] = 50, ['z'] = 51, ['0'] = 52, ['1'] = 53,876
['2'] = 54, ['3'] = 55, ['4'] = 56, ['5'] = 57, ['6'] = 58, ['7'] = 59,877
['8'] = 60, ['9'] = 61, ['+'] = 62, ['/'] = 63878
};880
size_t i = 0, j = 0;881
unsigned char array4[4], array3[3];882
int k = 0;884
while (i < len) {885
if (data[i] == '=' || data[i] == '\n' || data[i] == '\r' ||886
data[i] == ' ' || data[i] == '\t') {887
i++;888
continue;889
}891
array4[k++] = base64_table[(unsigned char)data[i++]];893
if (k == 4) {894
array3[0] = (array4[0] << 2) + ((array4[1] & 0x30) >> 4);895
array3[1] = ((array4[1] & 0x0f) << 4) + ((array4[2] & 0x3c) >> 2);896
array3[2] = ((array4[2] & 0x03) << 6) + array4[3];898
for (k = 0; k < 3 && j < out_len; k++)899
output[j++] = array3[k];900
k = 0;901
}902
}904
if (k) {905
for (int m = k; m < 4; m++)906
array4[m] = 0;908
array3[0] = (array4[0] << 2) + ((array4[1] & 0x30) >> 4);909
array3[1] = ((array4[1] & 0x0f) << 4) + ((array4[2] & 0x3c) >> 2);911
for (int m = 0; m < k - 1 && j < out_len; m++)912
output[j++] = array3[m];913
}915
Value result = sigil_make_bytevector(vm, j);916
if (sigil_is_bytevector(result)) {917
memcpy(sigil_bytevector_data(result), output, j);918
}919
free(output);920
return result;921
}923
/*924
* random-bytes count -> bytevector925
* Generate cryptographically secure random bytes.926
* Uses mbedTLS CTR-DRBG with OS entropy.927
*/928
static Value native_random_bytes(SigilVM *vm, int argc, Value *args)929
{930
(void)argc;932
if (!sigil_is_fixnum(args[0])) {933
sigil__vm_error(vm, SIGIL_ERR_TYPE, "random-bytes: expected integer");934
return SIGIL_UNDEFINED;935
}937
int64_t count = sigil_as_fixnum(args[0]);938
if (count < 0) {939
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: count must be non-negative");940
return SIGIL_UNDEFINED;941
}942
if (count > 65536) {943
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: count exceeds maximum (65536)");944
return SIGIL_UNDEFINED;945
}947
if (ensure_rng_initialized() != 0) {948
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: failed to initialize RNG");949
return SIGIL_UNDEFINED;950
}952
Value bv = sigil_make_bytevector(vm, (size_t)count);953
if (bv == SIGIL_UNDEFINED) return bv;955
unsigned char *data = sigil_bytevector_data(bv);956
int ret = mbedtls_ctr_drbg_random(&ctr_drbg_ctx, data, (size_t)count);957
if (ret != 0) {958
sigil__vm_error(vm, SIGIL_ERR_RUNTIME, "random-bytes: RNG failed");959
return SIGIL_UNDEFINED;960
}962
return bv;963
}965
/* ===========================================================966
* mbedTLS MPI — Big Integer Arithmetic967
*968
* Exposes mbedtls_mpi for arbitrary-precision integer operations969
* beyond the 63-bit fixnum range. Bytevectors in big-endian.970
* Functions allocate mpi contexts internally; no GC-visible handles.971
* =========================================================== */973
static int mpi_read_bv(mbedtls_mpi *X, Value bv_val)974
{975
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(bv_val);976
return mbedtls_mpi_read_binary(X, bv->data, bv->length);977
}979
static Value mpi_write_bv(SigilVM *vm, mbedtls_mpi *X, size_t size)980
{981
Value result = sigil_make_bytevector(vm, size);982
if (!sigil_is_bytevector(result)) return SIGIL_FALSE;983
int ret = mbedtls_mpi_write_binary(X, sigil_bytevector_data(result), size);984
if (ret != 0) return SIGIL_FALSE;985
return result;986
}988
/* MPI_BINOP generates add, sub, mul — the only difference is the op. */989
#define MPI_BINOP(method_name, op_fn) \990
static Value native_mpi_##method_name(SigilVM *vm, int argc, Value *args) \991
{ \992
(void)argc; \993
if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) { \994
sigil__vm_error(vm, SIGIL_ERR_TYPE, \995
"mpi-" #method_name ": expected bytevectors"); \996
return SIGIL_UNDEFINED; \997
} \998
if (!sigil_is_fixnum(args[2])) { \999
sigil__vm_error(vm, SIGIL_ERR_TYPE, \1000
"mpi-" #method_name ": expected integer size"); \1001
return SIGIL_UNDEFINED; \1002
} \1003
size_t size = (size_t)sigil_as_fixnum(args[2]); \1004
mbedtls_mpi A, B, R; \1005
mbedtls_mpi_init(&A); mbedtls_mpi_init(&B); mbedtls_mpi_init(&R); \1006
Value result = SIGIL_FALSE; \1007
if (mpi_read_bv(&A, args[0]) != 0) goto cleanup; \1008
if (mpi_read_bv(&B, args[1]) != 0) goto cleanup; \1009
if (op_fn(&R, &A, &B) != 0) goto cleanup; \1010
result = mpi_write_bv(vm, &R, size); \1011
cleanup: \1012
mbedtls_mpi_free(&R); mbedtls_mpi_free(&B); mbedtls_mpi_free(&A); \1013
return result; \1014
}1016
MPI_BINOP(add, mbedtls_mpi_add_mpi)1017
MPI_BINOP(sub, mbedtls_mpi_sub_mpi)1018
MPI_BINOP(mul, mbedtls_mpi_mul_mpi)1020
#undef MPI_BINOP1022
/*1023
* mpi-div a-bv b-bv size -> (cons quotient-bv remainder-bv)1024
*/1025
static Value native_mpi_div(SigilVM *vm, int argc, Value *args)1026
{1027
(void)argc;1029
if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {1030
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-div: expected bytevectors");1031
return SIGIL_UNDEFINED;1032
}1033
if (!sigil_is_fixnum(args[2])) {1034
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-div: expected integer size");1035
return SIGIL_UNDEFINED;1036
}1038
size_t size = (size_t)sigil_as_fixnum(args[2]);1040
mbedtls_mpi A, B, Q, R;1041
mbedtls_mpi_init(&A); mbedtls_mpi_init(&B);1042
mbedtls_mpi_init(&Q); mbedtls_mpi_init(&R);1044
Value result = SIGIL_FALSE;1046
if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;1047
if (mpi_read_bv(&B, args[1]) != 0) goto cleanup;1048
if (mbedtls_mpi_div_mpi(&Q, &R, &A, &B) != 0) goto cleanup;1050
Value q_bv = mpi_write_bv(vm, &Q, size);1051
Value r_bv = mpi_write_bv(vm, &R, size);1052
if (!sigil_is_bytevector(q_bv) || !sigil_is_bytevector(r_bv)) goto cleanup;1054
result = sigil_cons(vm, q_bv, r_bv);1056
cleanup:1057
mbedtls_mpi_free(&R); mbedtls_mpi_free(&Q);1058
mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);1059
return result;1060
}1062
/*1063
* mpi-mod a-bv n-bv size -> bytevector1064
*/1065
static Value native_mpi_mod(SigilVM *vm, int argc, Value *args)1066
{1067
(void)argc;1069
if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {1070
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod: expected bytevectors");1071
return SIGIL_UNDEFINED;1072
}1073
if (!sigil_is_fixnum(args[2])) {1074
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod: expected integer size");1075
return SIGIL_UNDEFINED;1076
}1078
size_t size = (size_t)sigil_as_fixnum(args[2]);1080
mbedtls_mpi A, N, R;1081
mbedtls_mpi_init(&A); mbedtls_mpi_init(&N); mbedtls_mpi_init(&R);1083
Value result = SIGIL_FALSE;1085
if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;1086
if (mpi_read_bv(&N, args[1]) != 0) goto cleanup;1087
if (mbedtls_mpi_mod_mpi(&R, &A, &N) != 0) goto cleanup;1089
result = mpi_write_bv(vm, &R, size);1091
cleanup:1092
mbedtls_mpi_free(&R); mbedtls_mpi_free(&N); mbedtls_mpi_free(&A);1093
return result;1094
}1096
/*1097
* mpi-mod-add a-bv b-bv n-bv size -> bytevector1098
* (a + b) mod n1099
*/1100
static Value native_mpi_mod_add(SigilVM *vm, int argc, Value *args)1101
{1102
(void)argc;1104
if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])1105
|| !sigil_is_bytevector(args[2])) {1106
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod-add: expected bytevectors");1107
return SIGIL_UNDEFINED;1108
}1109
if (!sigil_is_fixnum(args[3])) {1110
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-mod-add: expected integer size");1111
return SIGIL_UNDEFINED;1112
}1114
size_t size = (size_t)sigil_as_fixnum(args[3]);1116
mbedtls_mpi A, B, N, R;1117
mbedtls_mpi_init(&A); mbedtls_mpi_init(&B);1118
mbedtls_mpi_init(&N); mbedtls_mpi_init(&R);1120
Value result = SIGIL_FALSE;1122
if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;1123
if (mpi_read_bv(&B, args[1]) != 0) goto cleanup;1124
if (mpi_read_bv(&N, args[2]) != 0) goto cleanup;1125
if (mbedtls_mpi_add_mpi(&R, &A, &B) != 0) goto cleanup;1126
if (mbedtls_mpi_mod_mpi(&R, &R, &N) != 0) goto cleanup;1128
result = mpi_write_bv(vm, &R, size);1130
cleanup:1131
mbedtls_mpi_free(&R); mbedtls_mpi_free(&N);1132
mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);1133
return result;1134
}1136
/*1137
* mpi-inv-mod a-bv n-bv size -> bytevector | #f1138
*/1139
static Value native_mpi_inv_mod(SigilVM *vm, int argc, Value *args)1140
{1141
(void)argc;1143
if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {1144
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-inv-mod: expected bytevectors");1145
return SIGIL_UNDEFINED;1146
}1147
if (!sigil_is_fixnum(args[2])) {1148
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-inv-mod: expected integer size");1149
return SIGIL_UNDEFINED;1150
}1152
size_t size = (size_t)sigil_as_fixnum(args[2]);1154
mbedtls_mpi A, N, R;1155
mbedtls_mpi_init(&A); mbedtls_mpi_init(&N); mbedtls_mpi_init(&R);1157
Value result = SIGIL_FALSE;1159
if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;1160
if (mpi_read_bv(&N, args[1]) != 0) goto cleanup;1161
if (mbedtls_mpi_inv_mod(&R, &A, &N) != 0) goto cleanup;1163
result = mpi_write_bv(vm, &R, size);1165
cleanup:1166
mbedtls_mpi_free(&R); mbedtls_mpi_free(&N); mbedtls_mpi_free(&A);1167
return result;1168
}1170
/*1171
* mpi-cmp a-bv b-bv -> fixnum (-1, 0, or 1)1172
*/1173
static Value native_mpi_cmp(SigilVM *vm, int argc, Value *args)1174
{1175
(void)argc;1177
if (!sigil_is_bytevector(args[0]) || !sigil_is_bytevector(args[1])) {1178
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-cmp: expected bytevectors");1179
return SIGIL_UNDEFINED;1180
}1182
mbedtls_mpi A, B;1183
mbedtls_mpi_init(&A); mbedtls_mpi_init(&B);1185
if (mpi_read_bv(&A, args[0]) != 0) goto cleanup;1186
if (mpi_read_bv(&B, args[1]) != 0) goto cleanup;1188
int cmp = mbedtls_mpi_cmp_mpi(&A, &B);1189
mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);1190
return sigil_fixnum(cmp);1192
cleanup:1193
mbedtls_mpi_free(&B); mbedtls_mpi_free(&A);1194
return SIGIL_FALSE;1195
}1197
/*1198
* mpi-is-zero? a-bv -> boolean1199
*/1200
static Value native_mpi_is_zero(SigilVM *vm, int argc, Value *args)1201
{1202
(void)argc;1204
if (!sigil_is_bytevector(args[0])) {1205
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-is-zero?: expected bytevector");1206
return SIGIL_UNDEFINED;1207
}1209
mbedtls_mpi A;1210
mbedtls_mpi_init(&A);1212
if (mpi_read_bv(&A, args[0]) != 0) {1213
mbedtls_mpi_free(&A);1214
return SIGIL_FALSE;1215
}1217
int result = (mbedtls_mpi_cmp_int(&A, 0) == 0);1218
mbedtls_mpi_free(&A);1219
return result ? SIGIL_TRUE : SIGIL_FALSE;1220
}1222
/*1223
* mpi-shift-l a-bv bits size -> bytevector1224
*/1225
static Value native_mpi_shift_l(SigilVM *vm, int argc, Value *args)1226
{1227
(void)argc;1229
if (!sigil_is_bytevector(args[0])) {1230
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-l: expected bytevector");1231
return SIGIL_UNDEFINED;1232
}1233
if (!sigil_is_fixnum(args[1]) || !sigil_is_fixnum(args[2])) {1234
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-l: expected integer arguments");1235
return SIGIL_UNDEFINED;1236
}1238
int64_t bits_in = sigil_as_fixnum(args[1]);1239
int64_t size_in = sigil_as_fixnum(args[2]);1240
if (bits_in < 0 || size_in < 0) {1241
sigil__vm_error(vm, SIGIL_ERR_TYPE,1242
"mpi-shift-l: bits and size must be non-negative");1243
return SIGIL_UNDEFINED;1244
}1246
size_t bits = (size_t)bits_in;1247
size_t size = (size_t)size_in;1249
mbedtls_mpi A;1250
mbedtls_mpi_init(&A);1252
if (mpi_read_bv(&A, args[0]) != 0) {1253
mbedtls_mpi_free(&A);1254
return SIGIL_FALSE;1255
}1257
if (mbedtls_mpi_shift_l(&A, bits) != 0) {1258
mbedtls_mpi_free(&A);1259
return SIGIL_FALSE;1260
}1262
Value result = mpi_write_bv(vm, &A, size);1263
mbedtls_mpi_free(&A);1264
return result;1265
}1267
/*1268
* mpi-shift-r a-bv bits -> bytevector1269
*/1270
static Value native_mpi_shift_r(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-r: expected bytevector");1276
return SIGIL_UNDEFINED;1277
}1278
if (!sigil_is_fixnum(args[1])) {1279
sigil__vm_error(vm, SIGIL_ERR_TYPE, "mpi-shift-r: expected integer");1280
return SIGIL_UNDEFINED;1281
}1283
int64_t bits_in = sigil_as_fixnum(args[1]);1284
if (bits_in < 0) {1285
sigil__vm_error(vm, SIGIL_ERR_TYPE,1286
"mpi-shift-r: bits must be non-negative");1287
return SIGIL_UNDEFINED;1288
}1290
size_t bits = (size_t)bits_in;1292
mbedtls_mpi A;1293
mbedtls_mpi_init(&A);1295
if (mpi_read_bv(&A, args[0]) != 0) {1296
mbedtls_mpi_free(&A);1297
return SIGIL_FALSE;1298
}1300
if (mbedtls_mpi_shift_r(&A, bits) != 0) {1301
mbedtls_mpi_free(&A);1302
return SIGIL_FALSE;1303
}1305
SigilBytevector *in_bv = (SigilBytevector *)sigil_as_ptr(args[0]);1306
Value result = mpi_write_bv(vm, &A, in_bv->length);1307
mbedtls_mpi_free(&A);1308
return result;1309
}1311
/* ===========================================================1312
* ECDSA P-256, ECDH P-256, AES-128-GCM1313
*1314
* Used by Web Push (RFC 8291 / RFC 8292): VAPID JWT signs with1315
* ES256 (ECDSA P-256 + SHA-256), payload encryption derives1316
* shared secret via ECDH P-256 and seals with AES-128-GCM.1317
* =========================================================== */1319
#define ECDSA_P256_PRIV_LEN 321320
#define ECDSA_P256_PUB_LEN 65 /* Uncompressed: 0x04 || X(32) || Y(32) */1321
#define ECDSA_P256_SIG_LEN 64 /* JOSE format: r(32) || s(32) */1322
#define ECDH_P256_SECRET_LEN 321324
/*1325
* Extract bytes from a string-or-bytevector argument.1326
* On type mismatch raises a VM error and returns 0.1327
*/1328
static int crypto_read_bytes(SigilVM *vm, Value v, const char *fn,1329
const unsigned char **out_data, size_t *out_len)1330
{1331
if (sigil_is_string(v)) {1332
SigilString *s = (SigilString *)sigil_as_ptr(v);1333
*out_data = (const unsigned char *)s->data;1334
*out_len = s->byte_length;1335
return 1;1336
}1337
if (sigil_is_bytevector(v)) {1338
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(v);1339
*out_data = bv->data;1340
*out_len = bv->length;1341
return 1;1342
}1343
sigil__vm_error(vm, SIGIL_ERR_TYPE,1344
"expected string or bytevector argument");1345
(void)fn;1346
return 0;1347
}1349
/*1350
* Extract bytes from a bytevector-only argument with a required length.1351
* Returns 0 on type mismatch or wrong length (raises VM error).1352
*/1353
static int crypto_read_bv_exact(SigilVM *vm, Value v, size_t want,1354
const char *what,1355
const unsigned char **out_data)1356
{1357
if (!sigil_is_bytevector(v)) {1358
sigil__vm_error(vm, SIGIL_ERR_TYPE,1359
"expected bytevector argument");1360
(void)what;1361
return 0;1362
}1363
SigilBytevector *bv = (SigilBytevector *)sigil_as_ptr(v);1364
if (bv->length != want) {1365
sigil__vm_error(vm, SIGIL_ERR_RUNTIME,1366
"wrong bytevector length");1367
return 0;1368
}1369
*out_data = bv->data;1370
return 1;1371
}1373
/*1374
* ecdsa-p256-generate-keypair -> (cons priv-bv-32 pub-bv-65)1375
*1376
* Generates a fresh P-256 keypair. priv is the 32-byte big-endian1377
* scalar; pub is the 65-byte uncompressed-point encoding suitable1378
* for VAPID's `applicationServerKey` and for ECDH peer-key input.1379
*/1380
static Value native_ecdsa_p256_generate_keypair(SigilVM *vm, int argc, Value *args)1381
{1382
(void)argc; (void)args;1384
if (ensure_rng_initialized() != 0) {1385
sigil__vm_error(vm, SIGIL_ERR_RUNTIME,1386
"ecdsa-p256-generate-keypair: failed to init RNG");1387
return SIGIL_UNDEFINED;1388
}1390
mbedtls_ecp_group grp;1391
mbedtls_mpi d;1392
mbedtls_ecp_point Q;1393
mbedtls_ecp_group_init(&grp);1394
mbedtls_mpi_init(&d);1395
mbedtls_ecp_point_init(&Q);1397
Value result = SIGIL_FALSE;1398
int ret;1399
unsigned char priv_buf[ECDSA_P256_PRIV_LEN];1400
unsigned char pub_buf[ECDSA_P256_PUB_LEN];1401
size_t pub_olen = 0;1403
ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);1404
if (ret != 0) goto cleanup;1406
ret = mbedtls_ecp_gen_keypair(&grp, &d, &Q,1407
mbedtls_ctr_drbg_random, &ctr_drbg_ctx);1408
if (ret != 0) goto cleanup;1410
ret = mbedtls_mpi_write_binary(&d, priv_buf, ECDSA_P256_PRIV_LEN);1411
if (ret != 0) goto cleanup;1413
ret = mbedtls_ecp_point_write_binary(&grp, &Q,1414
MBEDTLS_ECP_PF_UNCOMPRESSED,1415
&pub_olen, pub_buf,1416
ECDSA_P256_PUB_LEN);1417
if (ret != 0 || pub_olen != ECDSA_P256_PUB_LEN) goto cleanup;1419
Value priv_bv = sigil_make_bytevector(vm, ECDSA_P256_PRIV_LEN);1420
Value pub_bv = sigil_make_bytevector(vm, ECDSA_P256_PUB_LEN);1421
if (!sigil_is_bytevector(priv_bv) || !sigil_is_bytevector(pub_bv)) {1422
goto cleanup;1423
}1424
memcpy(sigil_bytevector_data(priv_bv), priv_buf, ECDSA_P256_PRIV_LEN);1425
memcpy(sigil_bytevector_data(pub_bv), pub_buf, ECDSA_P256_PUB_LEN);1427
result = sigil_cons(vm, priv_bv, pub_bv);1429
cleanup:1430
/* Wipe stack copies of private material before unwinding. */1431
memset(priv_buf, 0, sizeof(priv_buf));1432
mbedtls_ecp_point_free(&Q);1433
mbedtls_mpi_free(&d);1434
mbedtls_ecp_group_free(&grp);1435
return result;1436
}1438
/*1439
* ecdsa-p256-sign priv-bv message -> sig-bv-64 | #f1440
*1441
* Hashes `message` with SHA-256, signs with ECDSA P-256 using the1442
* provided 32-byte private scalar, and returns the JOSE-format1443
* 64-byte signature (r || s, each 32 bytes big-endian). This is1444
* the format VAPID JWT (ES256) wants — NOT DER. Returns #f if1445
* the private key is invalid.1446
*/1447
static Value native_ecdsa_p256_sign(SigilVM *vm, int argc, Value *args)1448
{1449
(void)argc;1451
const unsigned char *priv_data;1452
if (!crypto_read_bv_exact(vm, args[0], ECDSA_P256_PRIV_LEN,1453
"private key", &priv_data)) {1454
return SIGIL_UNDEFINED;1455
}1457
const unsigned char *msg_data;1458
size_t msg_len;1459
if (!crypto_read_bytes(vm, args[1], "ecdsa-p256-sign",1460
&msg_data, &msg_len)) {1461
return SIGIL_UNDEFINED;1462
}1464
if (ensure_rng_initialized() != 0) {1465
sigil__vm_error(vm, SIGIL_ERR_RUNTIME,1466
"ecdsa-p256-sign: failed to init RNG");1467
return SIGIL_UNDEFINED;1468
}1470
/* SHA-256 the message into a 32-byte digest. */1471
unsigned char digest[32];1472
mbedtls_sha256_context sha;1473
mbedtls_sha256_init(&sha);1474
mbedtls_sha256_starts(&sha, 0);1475
mbedtls_sha256_update(&sha, msg_data, msg_len);1476
mbedtls_sha256_finish(&sha, digest);1477
mbedtls_sha256_free(&sha);1479
mbedtls_ecp_group grp;1480
mbedtls_mpi d, r, s;1481
mbedtls_ecp_group_init(&grp);1482
mbedtls_mpi_init(&d);1483
mbedtls_mpi_init(&r);1484
mbedtls_mpi_init(&s);1486
Value result = SIGIL_FALSE;1487
int ret;1488
unsigned char sig_buf[ECDSA_P256_SIG_LEN];1490
ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);1491
if (ret != 0) goto cleanup;1493
ret = mbedtls_mpi_read_binary(&d, priv_data, ECDSA_P256_PRIV_LEN);1494
if (ret != 0) goto cleanup;1496
/* Reject scalars outside [1, n-1] — mbedTLS doesn't validate1497
* for sign(); a zero d would silently produce an invalid sig. */1498
if (mbedtls_ecp_check_privkey(&grp, &d) != 0) goto cleanup;1500
ret = mbedtls_ecdsa_sign(&grp, &r, &s, &d,1501
digest, sizeof(digest),1502
mbedtls_ctr_drbg_random, &ctr_drbg_ctx);1503
if (ret != 0) goto cleanup;1505
ret = mbedtls_mpi_write_binary(&r, sig_buf, 32);1506
if (ret != 0) goto cleanup;1507
ret = mbedtls_mpi_write_binary(&s, sig_buf + 32, 32);1508
if (ret != 0) goto cleanup;1510
result = sigil_make_bytevector(vm, ECDSA_P256_SIG_LEN);1511
if (sigil_is_bytevector(result)) {1512
memcpy(sigil_bytevector_data(result), sig_buf, ECDSA_P256_SIG_LEN);1513
}1515
cleanup:1516
mbedtls_mpi_free(&s);1517
mbedtls_mpi_free(&r);1518
mbedtls_mpi_free(&d);1519
mbedtls_ecp_group_free(&grp);1520
return result;1521
}1523
/*1524
* ecdsa-p256-verify pub-bv message sig-bv -> boolean1525
*1526
* Returns #t when the JOSE-format 64-byte sig validates against1527
* the message under the given 65-byte uncompressed-point public key,1528
* otherwise #f. Hashes the message with SHA-256 internally so the1529
* caller passes the raw message body (matches sign's input shape).1530
*/1531
static Value native_ecdsa_p256_verify(SigilVM *vm, int argc, Value *args)1532
{1533
(void)argc;1535
const unsigned char *pub_data;1536
if (!crypto_read_bv_exact(vm, args[0], ECDSA_P256_PUB_LEN,1537
"public key", &pub_data)) {1538
return SIGIL_UNDEFINED;1539
}1541
const unsigned char *msg_data;1542
size_t msg_len;1543
if (!crypto_read_bytes(vm, args[1], "ecdsa-p256-verify",1544
&msg_data, &msg_len)) {1545
return SIGIL_UNDEFINED;1546
}1548
const unsigned char *sig_data;1549
if (!crypto_read_bv_exact(vm, args[2], ECDSA_P256_SIG_LEN,1550
"signature", &sig_data)) {1551
return SIGIL_UNDEFINED;1552
}1554
unsigned char digest[32];1555
mbedtls_sha256_context sha;1556
mbedtls_sha256_init(&sha);1557
mbedtls_sha256_starts(&sha, 0);1558
mbedtls_sha256_update(&sha, msg_data, msg_len);1559
mbedtls_sha256_finish(&sha, digest);1560
mbedtls_sha256_free(&sha);1562
mbedtls_ecp_group grp;1563
mbedtls_ecp_point Q;1564
mbedtls_mpi r, s;1565
mbedtls_ecp_group_init(&grp);1566
mbedtls_ecp_point_init(&Q);1567
mbedtls_mpi_init(&r);1568
mbedtls_mpi_init(&s);1570
Value result = SIGIL_FALSE;1571
int ret;1573
ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);1574
if (ret != 0) goto cleanup;1576
ret = mbedtls_ecp_point_read_binary(&grp, &Q, pub_data, ECDSA_P256_PUB_LEN);1577
if (ret != 0) goto cleanup;1579
/* Reject points off the curve / at infinity to avoid invalid-curve1580
* attacks — point_read_binary parses but does not validate. */1581
if (mbedtls_ecp_check_pubkey(&grp, &Q) != 0) goto cleanup;1583
ret = mbedtls_mpi_read_binary(&r, sig_data, 32);1584
if (ret != 0) goto cleanup;1585
ret = mbedtls_mpi_read_binary(&s, sig_data + 32, 32);1586
if (ret != 0) goto cleanup;1588
ret = mbedtls_ecdsa_verify(&grp, digest, sizeof(digest), &Q, &r, &s);1589
result = (ret == 0) ? SIGIL_TRUE : SIGIL_FALSE;1591
cleanup:1592
mbedtls_mpi_free(&s);1593
mbedtls_mpi_free(&r);1594
mbedtls_ecp_point_free(&Q);1595
mbedtls_ecp_group_free(&grp);1596
return result;1597
}1599
/*1600
* ecdh-p256-shared-secret priv-bv peer-pub-bv -> bytevector(32) | #f1601
*1602
* ECDH on P-256: derives the 32-byte big-endian X coordinate of1603
* (priv * peer_pub). The shared secret is the raw X coordinate per1604
* RFC 8291 (Web Push uses this directly as the IKM input to HKDF).1605
* Validates that peer-pub-bv is a valid point on the curve before1606
* computing — invalid-curve attack defence.1607
*/1608
static Value native_ecdh_p256_shared_secret(SigilVM *vm, int argc, Value *args)1609
{1610
(void)argc;1612
const unsigned char *priv_data;1613
if (!crypto_read_bv_exact(vm, args[0], ECDSA_P256_PRIV_LEN,1614
"private key", &priv_data)) {1615
return SIGIL_UNDEFINED;1616
}1618
const unsigned char *peer_data;1619
if (!crypto_read_bv_exact(vm, args[1], ECDSA_P256_PUB_LEN,1620
"peer public key", &peer_data)) {1621
return SIGIL_UNDEFINED;1622
}1624
if (ensure_rng_initialized() != 0) {1625
sigil__vm_error(vm, SIGIL_ERR_RUNTIME,1626
"ecdh-p256-shared-secret: failed to init RNG");1627
return SIGIL_UNDEFINED;1628
}1630
mbedtls_ecp_group grp;1631
mbedtls_mpi d, z;1632
mbedtls_ecp_point peer_Q;1633
mbedtls_ecp_group_init(&grp);1634
mbedtls_mpi_init(&d);1635
mbedtls_mpi_init(&z);1636
mbedtls_ecp_point_init(&peer_Q);1638
Value result = SIGIL_FALSE;1639
int ret;1640
unsigned char secret_buf[ECDH_P256_SECRET_LEN];1642
ret = mbedtls_ecp_group_load(&grp, MBEDTLS_ECP_DP_SECP256R1);1643
if (ret != 0) goto cleanup;1645
ret = mbedtls_mpi_read_binary(&d, priv_data, ECDSA_P256_PRIV_LEN);1646
if (ret != 0) goto cleanup;1647
if (mbedtls_ecp_check_privkey(&grp, &d) != 0) goto cleanup;1649
ret = mbedtls_ecp_point_read_binary(&grp, &peer_Q, peer_data,1650
ECDSA_P256_PUB_LEN);1651
if (ret != 0) goto cleanup;1652
if (mbedtls_ecp_check_pubkey(&grp, &peer_Q) != 0) goto cleanup;1654
ret = mbedtls_ecdh_compute_shared(&grp, &z, &peer_Q, &d,1655
mbedtls_ctr_drbg_random, &ctr_drbg_ctx);1656
if (ret != 0) goto cleanup;1658
ret = mbedtls_mpi_write_binary(&z, secret_buf, ECDH_P256_SECRET_LEN);1659
if (ret != 0) goto cleanup;1661
result = sigil_make_bytevector(vm, ECDH_P256_SECRET_LEN);1662
if (sigil_is_bytevector(result)) {1663
memcpy(sigil_bytevector_data(result), secret_buf,1664
ECDH_P256_SECRET_LEN);1665
}1667
cleanup:1668
memset(secret_buf, 0, sizeof(secret_buf));1669
mbedtls_ecp_point_free(&peer_Q);1670
mbedtls_mpi_free(&z);1671
mbedtls_mpi_free(&d);1672
mbedtls_ecp_group_free(&grp);1673
return result;1674
}1676
/*1677
* aes-128-gcm-encrypt key-bv-16 iv-bv-12 aad plaintext1678
* -> (cons ciphertext-bv tag-bv-16) | #f1679
*1680
* AAD and plaintext accept string or bytevector. Ciphertext length1681
* matches plaintext length; tag is always 16 bytes (full GCM tag).1682
* IV must be 12 bytes (the AEAD-recommended length, and what1683
* RFC 8291 § 3 prescribes).1684
*/1685
static Value native_aes_128_gcm_encrypt(SigilVM *vm, int argc, Value *args)1686
{1687
(void)argc;1689
const unsigned char *key_data;1690
if (!crypto_read_bv_exact(vm, args[0], 16, "key", &key_data)) {1691
return SIGIL_UNDEFINED;1692
}1694
const unsigned char *iv_data;1695
if (!crypto_read_bv_exact(vm, args[1], 12, "iv", &iv_data)) {1696
return SIGIL_UNDEFINED;1697
}1699
const unsigned char *aad_data;1700
size_t aad_len;1701
if (!crypto_read_bytes(vm, args[2], "aes-128-gcm-encrypt aad",1702
&aad_data, &aad_len)) {1703
return SIGIL_UNDEFINED;1704
}1706
const unsigned char *pt_data;1707
size_t pt_len;1708
if (!crypto_read_bytes(vm, args[3], "aes-128-gcm-encrypt plaintext",1709
&pt_data, &pt_len)) {1710
return SIGIL_UNDEFINED;1711
}1713
mbedtls_gcm_context ctx;1714
mbedtls_gcm_init(&ctx);1716
Value result = SIGIL_FALSE;1717
unsigned char tag_buf[16];1718
unsigned char *ct_buf = NULL;1720
int ret = mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key_data, 128);1721
if (ret != 0) goto cleanup;1723
ct_buf = (pt_len == 0) ? NULL : malloc(pt_len);1724
if (pt_len != 0 && !ct_buf) goto cleanup;1726
ret = mbedtls_gcm_crypt_and_tag(&ctx, MBEDTLS_GCM_ENCRYPT,1727
pt_len,1728
iv_data, 12,1729
aad_data, aad_len,1730
pt_data, ct_buf,1731
sizeof(tag_buf), tag_buf);1732
if (ret != 0) goto cleanup;1734
Value ct_bv = sigil_make_bytevector(vm, pt_len);1735
Value tag_bv = sigil_make_bytevector(vm, sizeof(tag_buf));1736
if (!sigil_is_bytevector(ct_bv) || !sigil_is_bytevector(tag_bv)) {1737
goto cleanup;1738
}1739
if (pt_len > 0) memcpy(sigil_bytevector_data(ct_bv), ct_buf, pt_len);1740
memcpy(sigil_bytevector_data(tag_bv), tag_buf, sizeof(tag_buf));1742
result = sigil_cons(vm, ct_bv, tag_bv);1744
cleanup:1745
if (ct_buf) free(ct_buf);1746
mbedtls_gcm_free(&ctx);1747
return result;1748
}1750
/*1751
* aes-128-gcm-decrypt key-bv-16 iv-bv-12 aad ciphertext-bv tag-bv-161752
* -> plaintext-bv | #f1753
*1754
* Returns #f on auth-tag mismatch (the classic AEAD failure). Used1755
* by the test path; production WEBPUSH only encrypts.1756
*/1757
static Value native_aes_128_gcm_decrypt(SigilVM *vm, int argc, Value *args)1758
{1759
(void)argc;1761
const unsigned char *key_data;1762
if (!crypto_read_bv_exact(vm, args[0], 16, "key", &key_data)) {1763
return SIGIL_UNDEFINED;1764
}1766
const unsigned char *iv_data;1767
if (!crypto_read_bv_exact(vm, args[1], 12, "iv", &iv_data)) {1768
return SIGIL_UNDEFINED;1769
}1771
const unsigned char *aad_data;1772
size_t aad_len;1773
if (!crypto_read_bytes(vm, args[2], "aes-128-gcm-decrypt aad",1774
&aad_data, &aad_len)) {1775
return SIGIL_UNDEFINED;1776
}1778
if (!sigil_is_bytevector(args[3])) {1779
sigil__vm_error(vm, SIGIL_ERR_TYPE,1780
"aes-128-gcm-decrypt: ciphertext must be bytevector");1781
return SIGIL_UNDEFINED;1782
}1783
SigilBytevector *ct_bv_in = (SigilBytevector *)sigil_as_ptr(args[3]);1784
const unsigned char *ct_data = ct_bv_in->data;1785
size_t ct_len = ct_bv_in->length;1787
const unsigned char *tag_data;1788
if (!crypto_read_bv_exact(vm, args[4], 16, "tag", &tag_data)) {1789
return SIGIL_UNDEFINED;1790
}1792
mbedtls_gcm_context ctx;1793
mbedtls_gcm_init(&ctx);1795
Value result = SIGIL_FALSE;1796
unsigned char *pt_buf = NULL;1798
int ret = mbedtls_gcm_setkey(&ctx, MBEDTLS_CIPHER_ID_AES, key_data, 128);1799
if (ret != 0) goto cleanup;1801
pt_buf = (ct_len == 0) ? NULL : malloc(ct_len);1802
if (ct_len != 0 && !pt_buf) goto cleanup;1804
ret = mbedtls_gcm_auth_decrypt(&ctx, ct_len,1805
iv_data, 12,1806
aad_data, aad_len,1807
tag_data, 16,1808
ct_data, pt_buf);1809
if (ret != 0) goto cleanup;1811
result = sigil_make_bytevector(vm, ct_len);1812
if (sigil_is_bytevector(result) && ct_len > 0) {1813
memcpy(sigil_bytevector_data(result), pt_buf, ct_len);1814
}1816
cleanup:1817
if (pt_buf) {1818
memset(pt_buf, 0, ct_len);1819
free(pt_buf);1820
}1821
mbedtls_gcm_free(&ctx);1822
return result;1823
}1826
/*1827
* Helper macro for module-scoped registration with export1828
*/1829
#define REGISTER_AND_EXPORT(name, func, arity, doc) \1830
sigil_module_register_native(vm, name, func, arity, doc); \1831
sigil_module_export(vm, name)1833
/*1834
* Initialize the (sigil crypto) module.1835
* This is called at VM startup.1836
*/1837
void sigil__init_sigil_crypto_module(SigilVM *vm)1838
{1839
SigilModule *module = sigil_begin_module(vm, "(sigil crypto)");1840
if (!module) return;1842
/* Hashing */1843
REGISTER_AND_EXPORT("sha1", native_sha1,1844
SIGIL_ARITY_EXACT(1), "Compute SHA-1 hash (returns bytevector)");1845
REGISTER_AND_EXPORT("sha256", native_sha256,1846
SIGIL_ARITY_EXACT(1), "Compute SHA-256 hash (hex string)");1847
REGISTER_AND_EXPORT("sha256-bytes", native_sha256_bytes,1848
SIGIL_ARITY_EXACT(1), "Compute SHA-256 hash (bytevector)");1849
REGISTER_AND_EXPORT("ripemd160", native_ripemd160,1850
SIGIL_ARITY_EXACT(1), "Compute RIPEMD-160 hash (returns bytevector)");1852
/* HMAC */1853
REGISTER_AND_EXPORT("hmac-sha256", native_hmac_sha256,1854
SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA256 (hex string)");1855
REGISTER_AND_EXPORT("hmac-sha256-bytes", native_hmac_sha256_bytes,1856
SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA256 (bytevector)");1857
REGISTER_AND_EXPORT("hmac-sha512-bytes", native_hmac_sha512_bytes,1858
SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA512 (bytevector)");1859
REGISTER_AND_EXPORT("hmac-sha1", native_hmac_sha1,1860
SIGIL_ARITY_EXACT(2), "Compute HMAC-SHA1 (returns bytevector)");1862
/* Key Derivation */1863
REGISTER_AND_EXPORT("pbkdf2-sha1", native_pbkdf2_sha1,1864
SIGIL_ARITY_EXACT(4), "Derive key using PBKDF2-HMAC-SHA1");1865
REGISTER_AND_EXPORT("pbkdf2-sha256", native_pbkdf2_sha256,1866
SIGIL_ARITY_EXACT(4), "Derive key using PBKDF2-HMAC-SHA256");1867
REGISTER_AND_EXPORT("pbkdf2-sha512", native_pbkdf2_sha512,1868
SIGIL_ARITY_EXACT(4), "Derive key using PBKDF2-HMAC-SHA512");1870
/* Base64 */1871
REGISTER_AND_EXPORT("base64-encode", native_base64_encode,1872
SIGIL_ARITY_EXACT(1), "Encode data as base64");1873
REGISTER_AND_EXPORT("base64-decode", native_base64_decode,1874
SIGIL_ARITY_EXACT(1), "Decode base64 string");1876
/* Random */1877
REGISTER_AND_EXPORT("random-bytes", native_random_bytes,1878
SIGIL_ARITY_EXACT(1), "Generate secure random bytes");1880
/* mbedTLS MPI — Big Integer Arithmetic */1881
REGISTER_AND_EXPORT("mpi-add", native_mpi_add,1882
SIGIL_ARITY_EXACT(3), "Add two bytevectors as big integers");1883
REGISTER_AND_EXPORT("mpi-sub", native_mpi_sub,1884
SIGIL_ARITY_EXACT(3), "Subtract two bytevectors as big integers");1885
REGISTER_AND_EXPORT("mpi-mul", native_mpi_mul,1886
SIGIL_ARITY_EXACT(3), "Multiply two bytevectors as big integers");1887
REGISTER_AND_EXPORT("mpi-div", native_mpi_div,1888
SIGIL_ARITY_EXACT(3), "Divide two bytevectors: returns (cons quotient remainder)");1889
REGISTER_AND_EXPORT("mpi-mod", native_mpi_mod,1890
SIGIL_ARITY_EXACT(3), "Modulo of two bytevectors as big integers");1891
REGISTER_AND_EXPORT("mpi-mod-add", native_mpi_mod_add,1892
SIGIL_ARITY_EXACT(4), "(a + b) mod n");1893
REGISTER_AND_EXPORT("mpi-inv-mod", native_mpi_inv_mod,1894
SIGIL_ARITY_EXACT(3), "Modular inverse of a modulo n");1895
REGISTER_AND_EXPORT("mpi-cmp", native_mpi_cmp,1896
SIGIL_ARITY_EXACT(2), "Compare two bytevectors: returns -1, 0, or 1");1897
REGISTER_AND_EXPORT("mpi-is-zero?", native_mpi_is_zero,1898
SIGIL_ARITY_EXACT(1), "Test if bytevectored big integer is zero");1899
REGISTER_AND_EXPORT("mpi-shift-l", native_mpi_shift_l,1900
SIGIL_ARITY_EXACT(3), "Left-shift bytevectored big integer");1901
REGISTER_AND_EXPORT("mpi-shift-r", native_mpi_shift_r,1902
SIGIL_ARITY_EXACT(2), "Right-shift bytevectored big integer");1904
/* ECDSA P-256 (VAPID JWT signing) */1905
REGISTER_AND_EXPORT("ecdsa-p256-generate-keypair",1906
native_ecdsa_p256_generate_keypair,1907
SIGIL_ARITY_EXACT(0),1908
"Generate ECDSA P-256 keypair: returns (cons priv-bv-32 pub-bv-65)");1909
REGISTER_AND_EXPORT("ecdsa-p256-sign", native_ecdsa_p256_sign,1910
SIGIL_ARITY_EXACT(2),1911
"Sign message with ECDSA P-256+SHA-256, JOSE format (r||s, 64 bytes)");1912
REGISTER_AND_EXPORT("ecdsa-p256-verify", native_ecdsa_p256_verify,1913
SIGIL_ARITY_EXACT(3),1914
"Verify ECDSA P-256+SHA-256 signature (JOSE format)");1916
/* ECDH P-256 (Web Push shared-secret derivation) */1917
REGISTER_AND_EXPORT("ecdh-p256-shared-secret",1918
native_ecdh_p256_shared_secret,1919
SIGIL_ARITY_EXACT(2),1920
"ECDH P-256: 32-byte X coordinate of priv*peer-pub");1922
/* AES-128-GCM (Web Push payload envelope) */1923
REGISTER_AND_EXPORT("aes-128-gcm-encrypt", native_aes_128_gcm_encrypt,1924
SIGIL_ARITY_EXACT(4),1925
"AES-128-GCM encrypt: returns (cons ciphertext tag)");1926
REGISTER_AND_EXPORT("aes-128-gcm-decrypt", native_aes_128_gcm_decrypt,1927
SIGIL_ARITY_EXACT(5),1928
"AES-128-GCM decrypt: returns plaintext or #f on auth fail");1930
sigil_end_module(vm);1931
}1933
#undef REGISTER_AND_EXPORT1935
#endif /* !__EMSCRIPTEN__ */