Files
cell/scripts/crypto.c
2025-11-29 14:22:19 -06:00

186 lines
5.3 KiB
C

#include "cell.h"
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include "monocypher.h"
static inline void to_hex(const uint8_t *in, size_t in_len, char *out)
{
static const char hexchars[] = "0123456789abcdef";
for (size_t i = 0; i < in_len; i++) {
out[2*i ] = hexchars[(in[i] >> 4) & 0x0F];
out[2*i + 1] = hexchars[ in[i] & 0x0F];
}
out[2 * in_len] = '\0'; // null-terminate
}
static inline int nibble_from_char(char c, uint8_t *nibble)
{
if (c >= '0' && c <= '9') { *nibble = (uint8_t)(c - '0'); return 0; }
if (c >= 'a' && c <= 'f') { *nibble = (uint8_t)(c - 'a' + 10); return 0; }
if (c >= 'A' && c <= 'F') { *nibble = (uint8_t)(c - 'A' + 10); return 0; }
return -1; // invalid char
}
static inline int from_hex(const char *hex, uint8_t *out, size_t out_len)
{
for (size_t i = 0; i < out_len; i++) {
uint8_t hi, lo;
if (nibble_from_char(hex[2*i], &hi) < 0) return -1;
if (nibble_from_char(hex[2*i + 1], &lo) < 0) return -1;
out[i] = (uint8_t)((hi << 4) | lo);
}
return 0;
}
// Convert a JSValue containing a 64-character hex string into a 32-byte array.
static inline void js2crypto(JSContext *js, JSValue v, uint8_t *crypto)
{
size_t hex_len;
const char *hex_str = JS_ToCStringLen(js, &hex_len, v);
if (!hex_str)
return;
if (hex_len != 64) {
JS_FreeCString(js, hex_str);
JS_ThrowTypeError(js, "js2crypto: expected 64-hex-char string");
return;
}
if (from_hex(hex_str, crypto, 32) < 0) {
JS_FreeCString(js, hex_str);
JS_ThrowTypeError(js, "js2crypto: invalid hex encoding");
return;
}
JS_FreeCString(js, hex_str);
}
static inline JSValue crypto2js(JSContext *js, const uint8_t *crypto)
{
char hex[65]; // 32*2 + 1 for null terminator
to_hex(crypto, 32, hex);
return JS_NewString(js, hex);
}
JSValue js_crypto_keypair(JSContext *js, JSValue self, int argc, JSValue *argv) {
JSValue ret = JS_NewObject(js);
uint8_t public[32];
uint8_t private[32];
JSValue global = JS_GetGlobalObject(js);
JSValue os = JS_GetPropertyStr(js, global, "os");
JSValue random_blob = JS_GetPropertyStr(js, os, "random_blob");
JSValue size_val = JS_NewInt32(js, 32);
JSValue blob = JS_Call(js, random_blob, os, 1, &size_val);
size_t len;
uint8_t *data = js_get_blob_data(js, &len, blob);
if (!data || len != 32) {
JS_FreeValue(js, blob);
JS_FreeValue(js, size_val);
JS_FreeValue(js, random_blob);
JS_FreeValue(js, os);
JS_FreeValue(js, global);
return JS_ThrowInternalError(js, "failed to get random bytes");
}
memcpy(private, data, 32);
JS_FreeValue(js, blob);
JS_FreeValue(js, size_val);
JS_FreeValue(js, random_blob);
JS_FreeValue(js, os);
JS_FreeValue(js, global);
private[0] &= 248;
private[31] &= 127;
private[31] |= 64;
crypto_x25519_public_key(public,private);
JS_SetPropertyStr(js, ret, "public", crypto2js(js, public));
JS_SetPropertyStr(js, ret, "private", crypto2js(js,private));
return ret;
}
JSValue js_crypto_shared(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 1 || !JS_IsObject(argv[0])) {
return JS_ThrowTypeError(js, "crypto.shared: expected an object argument");
}
JSValue obj = argv[0];
JSValue val_pub = JS_GetPropertyStr(js, obj, "public");
if (JS_IsException(val_pub)) {
JS_FreeValue(js, val_pub);
return JS_EXCEPTION;
}
JSValue val_priv = JS_GetPropertyStr(js, obj, "private");
if (JS_IsException(val_priv)) {
JS_FreeValue(js, val_pub);
JS_FreeValue(js, val_priv);
return JS_EXCEPTION;
}
uint8_t pub[32], priv[32];
js2crypto(js, val_pub, pub);
js2crypto(js, val_priv, priv);
JS_FreeValue(js, val_pub);
JS_FreeValue(js, val_priv);
uint8_t shared[32];
crypto_x25519(shared, priv, pub);
return crypto2js(js, shared);
}
JSValue js_crypto_hash(JSContext *js, JSValue self, int argc, JSValue *argv)
{
if (argc < 1)
return JS_ThrowTypeError(js, "hash requires at least one argument");
// Get input data
size_t data_len;
void *data = js_get_blob_data(js, &data_len, argv[0]);
if (!data)
return JS_ThrowTypeError(js, "hash: first argument must be an ArrayBuffer");
// Get hash length (default 32)
int32_t hash_len = 32;
if (argc > 1) {
if (JS_ToInt32(js, &hash_len, argv[1]))
return JS_EXCEPTION;
if (hash_len < 1 || hash_len > 64)
return JS_ThrowRangeError(js, "hash length must be between 1 and 64");
}
// Allocate output buffer
uint8_t *hash = js_malloc(js, hash_len);
if (!hash)
return JS_EXCEPTION;
// Compute BLAKE2b hash
crypto_blake2b(hash, hash_len, data, data_len);
// Return as blob
JSValue result = js_new_blob_stoned_copy(js, hash, hash_len);
js_free(js, hash);
return result;
}
static const JSCFunctionListEntry js_crypto_funcs[] = {
JS_CFUNC_DEF("keypair", 0, js_crypto_keypair),
JS_CFUNC_DEF("shared", 1, js_crypto_shared),
JS_CFUNC_DEF("hash", 2, js_crypto_hash),
};
JSValue js_crypto_use(JSContext *js)
{
JSValue obj = JS_NewObject(js);
JS_SetPropertyFunctionList(js, obj, js_crypto_funcs, sizeof(js_crypto_funcs)/sizeof(js_crypto_funcs[0]));
return obj;
}