commit df0f841d1612194f6e799f9f418d68b333ac1ac2
parent b577ea6aeb16f983f05ddd081aa8c9d681932d41
Author: Florian Dold <florian@dold.me>
Date: Wed, 16 Nov 2022 16:50:50 +0100
crypto ops
Diffstat:
| M | prelude.js | | | 1 | + |
| M | quickjs-libc.c | | | 189 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------- |
| M | quickjs.c | | | 96 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------- |
| M | quickjs.h | | | 1 | + |
4 files changed, 236 insertions(+), 51 deletions(-)
diff --git a/prelude.js b/prelude.js
@@ -25,3 +25,4 @@ globalThis.setImmediate = (f) => os.setTimeout(f, 0);
console.info = (...args) => { console.log(...args); };
console.warn = (...args) => { console.log(...args); };
console.error = (...args) => { console.log(...args); };
+console.assert = (b) => { if (!b) throw Error("assertion failed") };
diff --git a/quickjs-libc.c b/quickjs-libc.c
@@ -4098,9 +4098,14 @@ static JSValue js_talercrypto_encode_crock(JSContext *ctx, JSValue this_val,
}
assert (size < SIZE_MAX / 8 - 4);
- out_size = (size * 8 + 4) / 5;
+ out_size = size * 8;
- out = malloc (out_size);
+ if (out_size % 5 > 0)
+ out_size += 5 - out_size % 5;
+ out_size /= 5;
+
+ out = malloc (out_size + 1);
+ memset (out, 0, out_size + 1);
if (!out) {
goto exception;
}
@@ -4137,7 +4142,7 @@ static JSValue js_talercrypto_encode_crock(JSContext *ctx, JSValue this_val,
if (wpos < out_size)
out[wpos] = '\0';
- ret_val = JS_NewString(ctx, out);
+ ret_val = JS_NewString(ctx, (char *) out);
done:
if (NULL != out) {
@@ -4164,6 +4169,7 @@ static JSValue js_talercrypto_decode_crock(JSContext *ctx, JSValue this_val,
JSValue ret_val = JS_UNDEFINED;
unsigned char *uout = NULL;
size_t out_size;
+ JSValue abuf;
enc = JS_ToCStringLen2(ctx, &enclen, argv[0], FALSE);
if (!enc) {
@@ -4171,9 +4177,9 @@ static JSValue js_talercrypto_decode_crock(JSContext *ctx, JSValue this_val,
}
out_size = (enclen * 5) / 8;
+ encoded_len = out_size * 8;
uout = malloc(out_size);
assert (out_size < SIZE_MAX / 8);
- encoded_len = out_size * 8;
wpos = out_size;
rpos = enclen;
if ((encoded_len % 5) > 0)
@@ -4189,10 +4195,12 @@ static JSValue js_talercrypto_decode_crock(JSContext *ctx, JSValue this_val,
bits = (ret = getValue__ (enc[--rpos]));
}
if ((encoded_len + shift) / 5 != enclen) {
+ JS_ThrowTypeError(ctx, "wrong encoded length");
goto exception;
}
if (-1 == ret) {
+ JS_ThrowTypeError(ctx, "invalid character in encoding");
goto exception;
}
while (wpos > 0)
@@ -4214,9 +4222,14 @@ static JSValue js_talercrypto_decode_crock(JSContext *ctx, JSValue this_val,
}
}
if ((0 != rpos) || (0 != vbit)) {
+ JS_ThrowTypeError(ctx, "rpos or vbit not zero");
goto exception;
}
- ret_val = JS_NewArrayBufferCopy(ctx, uout, out_size);
+ abuf = JS_NewArrayBufferCopy(ctx, uout, out_size);
+ if (JS_IsException(abuf)) {
+ goto exception;
+ }
+ ret_val = JS_NewTypedArray(ctx, abuf, 1);
done:
JS_FreeCString(ctx, enc);
if (uout) {
@@ -4228,30 +4241,136 @@ exception:
goto done;
}
-//
-///**
-// * eddsaKeyCreate: () => ArrayBuffer
-// */
-//static JSValue js_talercrypto_eddsa_key_create(JSContext *ctx, JSValue this_val,
-// int argc, JSValueConst *argv)
-//{
-//}
-//
-//static JSValue js_talercrypto_eddsa_key_get_public(JSContext *ctx, JSValue this_val,
-// int argc, JSValueConst *argv)
-//{
-//}
-//
-//static JSValue js_talercrypto_eddsa_sign(JSContext *ctx, JSValue this_val,
-// int argc, JSValueConst *argv)
-//{
-//}
-//
-//static JSValue js_talercrypto_eddsa_verify(JSContext *ctx, JSValue this_val,
-// int argc, JSValueConst *argv)
-//{
-//}
-//
+static JSValue make_js_ta_copy(JSContext *ctx, uint8_t *data, size_t size)
+{
+ JSValue array_buf;
+
+ array_buf = JS_NewArrayBufferCopy(ctx, data, size);
+ if (JS_IsException(array_buf)) {
+ return JS_EXCEPTION;
+ }
+ return JS_NewTypedArray(ctx, array_buf, 1);
+}
+
+static JSValue js_talercrypto_hash(JSContext *ctx, JSValue this_val,
+ int argc, JSValueConst *argv)
+{
+ size_t size;
+ uint8_t *buf;
+ unsigned char h[crypto_hash_BYTES];
+
+ buf = JS_GetArrayBuffer(ctx, &size, argv[0]);
+ if (!buf) {
+ return JS_EXCEPTION;
+ }
+ crypto_hash(h, buf, size);
+
+ return make_js_ta_copy(ctx, h, crypto_hash_BYTES);
+}
+
+static JSValue js_talercrypto_eddsa_key_get_public(JSContext *ctx, JSValue this_val,
+ int argc, JSValueConst *argv)
+{
+ size_t size;
+ uint8_t *buf;
+ unsigned char pk[crypto_sign_PUBLICKEYBYTES];
+ unsigned char sk[crypto_sign_SECRETKEYBYTES];
+
+ buf = JS_GetArrayBuffer(ctx, &size, argv[0]);
+
+ if (!buf) {
+ return JS_EXCEPTION;
+ }
+ if (size != 32) {
+ return JS_ThrowTypeError(ctx, "invalid private key size");
+ }
+
+ crypto_sign_seed_keypair(pk, sk, buf);
+ // FIXME: clean up stack!
+
+ return make_js_ta_copy(ctx, pk, crypto_sign_PUBLICKEYBYTES);
+}
+
+/**
+ * (msg, priv) => sig
+ */
+static JSValue js_talercrypto_eddsa_sign(JSContext *ctx, JSValue this_val,
+ int argc, JSValueConst *argv)
+{
+ unsigned char *seed;
+ size_t seed_size;
+ unsigned char *data;
+ size_t data_size;
+ unsigned char sk[crypto_sign_SECRETKEYBYTES];
+ unsigned char pk[crypto_sign_PUBLICKEYBYTES];
+ unsigned char sig[64];
+ int res;
+
+ data = JS_GetArrayBuffer(ctx, &data_size, argv[0]);
+ if (!data) {
+ return JS_EXCEPTION;
+ }
+
+ seed = JS_GetArrayBuffer(ctx, &seed_size, argv[1]);
+ if (!seed) {
+ return JS_EXCEPTION;
+ }
+ if (seed_size != 32) {
+ return JS_ThrowTypeError(ctx, "invalid private key size");
+ }
+
+ if (0 != crypto_sign_seed_keypair (pk, sk, seed)) {
+ return JS_EXCEPTION;
+ }
+
+ res = crypto_sign_detached ((uint8_t *) sig,
+ NULL,
+ (uint8_t *) data,
+ data_size,
+ sk);
+ if (res != 0) {
+ return JS_EXCEPTION;
+ }
+ return make_js_ta_copy(ctx, sig, 64);
+}
+
+/**
+ * (msg, sig, pub) -> bool
+ */
+static JSValue js_talercrypto_eddsa_verify(JSContext *ctx, JSValue this_val,
+ int argc, JSValueConst *argv)
+{
+ unsigned char *msg;
+ size_t msg_size;
+ unsigned char *sig;
+ size_t sig_size;
+ unsigned char *pub;
+ size_t pub_size;
+ int res;
+
+ msg = JS_GetArrayBuffer(ctx, &msg_size, argv[0]);
+ if (!msg) {
+ return JS_EXCEPTION;
+ }
+ sig = JS_GetArrayBuffer(ctx, &sig_size, argv[1]);
+ if (!sig) {
+ return JS_EXCEPTION;
+ }
+ if (sig_size != 64) {
+ return JS_ThrowTypeError(ctx, "invalid signature size");
+ }
+ pub = JS_GetArrayBuffer(ctx, &pub_size, argv[2]);
+ if (!pub) {
+ return JS_EXCEPTION;
+ }
+ if (pub_size != 32) {
+ return JS_ThrowTypeError(ctx, "invalid public key size");
+ }
+
+ res = crypto_sign_verify_detached (sig, msg, msg_size, pub);
+ return (res == 0) ? JS_TRUE : JS_FALSE;
+}
+
//static JSValue js_talercrypto_kx_ecdhe_eddsa(JSContext *ctx, JSValue this_val,
// int argc, JSValueConst *argv)
//{
@@ -4262,10 +4381,6 @@ exception:
//{
//}
//
-//static JSValue js_talercrypto_hash(JSContext *ctx, JSValue this_val,
-// int argc, JSValueConst *argv)
-//{
-//}
static JSValue js_decode_utf8(JSContext *ctx, JSValueConst this_val,
int argc, JSValueConst *argv)
@@ -4304,6 +4419,14 @@ void js_std_add_helpers(JSContext *ctx, int argc, char **argv)
JS_NewCFunction(ctx, js_talercrypto_encode_crock, "_encodeCrock", 1));
JS_SetPropertyStr(ctx, global_obj, "_decodeCrock",
JS_NewCFunction(ctx, js_talercrypto_decode_crock, "_decodeCrock", 1));
+ JS_SetPropertyStr(ctx, global_obj, "_hash",
+ JS_NewCFunction(ctx, js_talercrypto_hash, "_hash", 1));
+ JS_SetPropertyStr(ctx, global_obj, "_eddsaGetPublic",
+ JS_NewCFunction(ctx, js_talercrypto_eddsa_key_get_public, "_eddsaGetPublic", 1));
+ JS_SetPropertyStr(ctx, global_obj, "_eddsaSign",
+ JS_NewCFunction(ctx, js_talercrypto_eddsa_sign, "_eddsaSign", 2));
+ JS_SetPropertyStr(ctx, global_obj, "_eddsaVerify",
+ JS_NewCFunction(ctx, js_talercrypto_eddsa_verify, "_eddsaVerify", 3));
/* same methods as the mozilla JS shell */
if (argc >= 0) {
diff --git a/quickjs.c b/quickjs.c
@@ -51355,6 +51355,20 @@ void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj)
}
}
+/* check if obj is ArrayBuffer or SharedArrayBuffer */
+static BOOL js_is_array_buffer(JSContext *ctx, JSValueConst obj)
+{
+ JSObject *p;
+ if (JS_VALUE_GET_TAG(obj) != JS_TAG_OBJECT)
+ return FALSE;
+ p = JS_VALUE_GET_OBJ(obj);
+ if (p->class_id != JS_CLASS_ARRAY_BUFFER &&
+ p->class_id != JS_CLASS_SHARED_ARRAY_BUFFER) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
/* get an ArrayBuffer or SharedArrayBuffer */
static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj)
{
@@ -51371,24 +51385,6 @@ static JSArrayBuffer *js_get_array_buffer(JSContext *ctx, JSValueConst obj)
return p->u.array_buffer;
}
-/* return NULL if exception. WARNING: any JS call can detach the
- buffer and render the returned pointer invalid */
-uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj)
-{
- JSArrayBuffer *abuf = js_get_array_buffer(ctx, obj);
- if (!abuf)
- goto fail;
- if (abuf->detached) {
- JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
- goto fail;
- }
- *psize = abuf->byte_length;
- return abuf->data;
- fail:
- *psize = 0;
- return NULL;
-}
-
static JSValue js_array_buffer_slice(JSContext *ctx,
JSValueConst this_val,
int argc, JSValueConst *argv, int class_id)
@@ -51614,6 +51610,50 @@ JSValue JS_GetTypedArrayBuffer(JSContext *ctx, JSValueConst obj,
}
return JS_DupValue(ctx, JS_MKPTR(JS_TAG_OBJECT, ta->buffer));
}
+
+/* return NULL if exception. WARNING: any JS call can detach the
+ buffer and render the returned pointer invalid */
+uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj)
+{
+ if (js_is_array_buffer(ctx, obj)) {
+ JSArrayBuffer *abuf = js_get_array_buffer(ctx, obj);
+ if (!abuf)
+ goto fail;
+ if (abuf->detached) {
+ JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+ goto fail;
+ }
+ *psize = abuf->byte_length;
+ return abuf->data;
+ } else {
+ JSObject *p;
+ JSTypedArray *ta;
+ JSArrayBuffer *abuf;
+ p = get_typed_array(ctx, obj, FALSE);
+ if (!p) {
+ goto fail;
+ }
+ if (typed_array_is_detached(ctx, p)) {
+ JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+ goto fail;
+ }
+ ta = p->u.typed_array;
+ abuf = ta->buffer->u.array_buffer;
+ if (!abuf)
+ goto fail;
+ if (abuf->detached) {
+ JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+ goto fail;
+ }
+ *psize = abuf->byte_length;
+ return abuf->data;
+ }
+ JS_ThrowTypeError(ctx, "expected ArrayBuffer or ArrayBufferView");
+ fail:
+ *psize = 0;
+ return NULL;
+}
+
static JSValue js_typed_array_get_toStringTag(JSContext *ctx,
JSValueConst this_val)
@@ -52964,6 +53004,26 @@ static int typed_array_init(JSContext *ctx, JSValueConst obj,
return 0;
}
+JSValue JS_NewTypedArray(JSContext *ctx, JSValue array_buf, size_t bytes_per_element)
+{
+ JSValue obj;
+ JSObject *p = JS_VALUE_GET_OBJ(array_buf);
+ JSArrayBuffer *abuf = NULL;
+ if (p->class_id != JS_CLASS_ARRAY_BUFFER) {
+ return JS_ThrowTypeError(ctx, "expected array buffer");
+ }
+ abuf = p->u.array_buffer;
+ if (abuf->detached) {
+ return JS_ThrowTypeErrorDetachedArrayBuffer(ctx);
+ }
+ obj = JS_NewObjectClass(ctx, JS_CLASS_UINT8_ARRAY);
+ if (typed_array_init(ctx, obj, array_buf, 0, abuf->byte_length)) {
+ JS_FreeValue(ctx, obj);
+ return JS_EXCEPTION;
+ }
+ return obj;
+}
+
static JSValue js_array_from_iterator(JSContext *ctx, uint32_t *plen,
JSValueConst obj, JSValueConst method)
diff --git a/quickjs.h b/quickjs.h
@@ -818,6 +818,7 @@ typedef void JSFreeArrayBufferDataFunc(JSRuntime *rt, void *opaque, void *ptr);
JSValue JS_NewArrayBuffer(JSContext *ctx, uint8_t *buf, size_t len,
JSFreeArrayBufferDataFunc *free_func, void *opaque,
JS_BOOL is_shared);
+JSValue JS_NewTypedArray(JSContext *ctx, JSValue array_buf, size_t bytes_per_element);
JSValue JS_NewArrayBufferCopy(JSContext *ctx, const uint8_t *buf, size_t len);
void JS_DetachArrayBuffer(JSContext *ctx, JSValueConst obj);
uint8_t *JS_GetArrayBuffer(JSContext *ctx, size_t *psize, JSValueConst obj);