quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

commit df0f841d1612194f6e799f9f418d68b333ac1ac2
parent b577ea6aeb16f983f05ddd081aa8c9d681932d41
Author: Florian Dold <florian@dold.me>
Date:   Wed, 16 Nov 2022 16:50:50 +0100

crypto ops

Diffstat:
Mprelude.js | 1+
Mquickjs-libc.c | 189+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mquickjs.c | 96++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------
Mquickjs.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);