From 30e22a7f5627e2ee4528d3678d3bc5511cbf1580 Mon Sep 17 00:00:00 2001 From: Lyndon Date: Mon, 6 Jan 2025 11:56:24 +0800 Subject: [PATCH] feat: add more bindings --- Makefile | 2 - src/secp256k1_module.c | 123 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 111 insertions(+), 14 deletions(-) diff --git a/Makefile b/Makefile index f8f8ee2..a67e72a 100644 --- a/Makefile +++ b/Makefile @@ -68,8 +68,6 @@ CFLAGS_BASE_SECP256k1 = $(CFLAGS_BASE) \ LDFLAGS := -static --gc-sections LDFLAGS += -Ldeps/compiler-rt-builtins-riscv/build -lcompiler-rt -SECP256K1_SRC := deps/secp256k1/src/precomputed_ecmult.c - all: out build/ckb-js-vm out: diff --git a/src/secp256k1_module.c b/src/secp256k1_module.c index 60fe8bf..941fcc4 100644 --- a/src/secp256k1_module.c +++ b/src/secp256k1_module.c @@ -1,22 +1,23 @@ +#include #include "cutils.h" #include "secp256k1_module.h" #include "secp256k1.h" #include "secp256k1_recovery.h" #include "group.h" -#undef CHECK -#undef CHECK2 -#include "qjs.h" + +#define COMPRESSED_PUBKEY_LENGTH 33 // 1 byte prefix + 32 bytes x coordinate +#define UNCOMPRESSED_PUBKEY_LENGTH 65 // 1 byte prefix + 32 bytes x + 32 bytes y // from the secp256k1 library. The original table is used for signing operations and // public key generation, and consumes significant memory. Since we only need verification // functionality in our on-chain scripts (not signing or key generation), we can safely // use this minimal placeholder to reduce memory usage. -const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[1][1]; +const secp256k1_ge_storage secp256k1_ecmult_gen_prec_table[0][0]; const secp256k1_context *g_secp256k1_context = NULL; +static void free_array_buffer(JSRuntime *rt, void *_opaque, void *ptr) { js_free_rt(rt, ptr); } + static JSValue recover(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { - int err = 0; - JSValue ret = JS_EXCEPTION; size_t msg_len, sig_len; secp256k1_pubkey pubkey; secp256k1_ecdsa_recoverable_signature signature; @@ -49,18 +50,116 @@ static JSValue recover(JSContext *ctx, JSValueConst this_val, int argc, JSValueC // Perform the recovery int success = secp256k1_ecdsa_recover(g_secp256k1_context, &pubkey, &signature, msg); - CHECK2(success, -1); - + if (!success) { + return JS_ThrowInternalError(ctx, "invalid signature"); + } // Return the public key with 64-bytes - ret = JS_NewArrayBufferCopy(ctx, pubkey.data, sizeof(pubkey)); + return JS_NewArrayBufferCopy(ctx, pubkey.data, sizeof(pubkey)); +} + +static JSValue serialize_pubkey(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + size_t pubkey_len; + uint8_t *pubkey_data; + secp256k1_pubkey pubkey; + unsigned int flags = SECP256K1_EC_COMPRESSED; + + // Check argument count + if (argc != 2) return JS_ThrowTypeError(ctx, "wrong number of arguments"); + + // Get public key from first argument + pubkey_data = JS_GetArrayBuffer(ctx, &pubkey_len, argv[0]); + if (!pubkey_data || pubkey_len != sizeof(secp256k1_pubkey)) { + return JS_ThrowTypeError(ctx, "invalid public key format"); + } + + // Copy the public key data + memcpy(&pubkey, pubkey_data, sizeof(secp256k1_pubkey)); + + // Get compression flag from second argument if provided + flags = JS_ToBool(ctx, argv[1]) ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED; + + // Allocate output buffer (33 bytes for compressed, 65 for uncompressed) + size_t output_len = flags == SECP256K1_EC_COMPRESSED ? COMPRESSED_PUBKEY_LENGTH : UNCOMPRESSED_PUBKEY_LENGTH; + uint8_t *output = js_malloc(ctx, output_len); + if (!output) { + return JS_ThrowOutOfMemory(ctx); + } + + // Serialize the public key + size_t serialized_len = output_len; + if (!secp256k1_ec_pubkey_serialize(g_secp256k1_context, output, &serialized_len, &pubkey, flags)) { + js_free(ctx, output); + return JS_ThrowInternalError(ctx, "serialization failed"); + } + + return JS_NewArrayBuffer(ctx, output, serialized_len, free_array_buffer, ctx, false); +} + +static JSValue parse_pubkey(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + size_t input_len; + uint8_t *input; + secp256k1_pubkey pubkey; + + // Check argument count + if (argc != 1) return JS_ThrowTypeError(ctx, "wrong number of arguments"); + + // Get serialized public key from argument + input = JS_GetArrayBuffer(ctx, &input_len, argv[0]); + if (!input || (input_len != COMPRESSED_PUBKEY_LENGTH && input_len != UNCOMPRESSED_PUBKEY_LENGTH)) { + return JS_ThrowTypeError(ctx, "invalid public key format"); + } + + // Parse the public key + if (!secp256k1_ec_pubkey_parse(g_secp256k1_context, &pubkey, input, input_len)) { + return JS_ThrowTypeError(ctx, "invalid public key"); + } + + // Return the parsed public key + return JS_NewArrayBufferCopy(ctx, pubkey.data, sizeof(pubkey)); +} + +static JSValue verify(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + size_t msg_len, sig_len, pubkey_len; + uint8_t *msg = NULL, *sig = NULL, *pubkey_data = NULL; + secp256k1_pubkey pubkey; + secp256k1_ecdsa_signature signature; + + // Check argument count + if (argc != 3) return JS_ThrowTypeError(ctx, "wrong number of arguments"); + + // Get signature from first argument + sig = JS_GetArrayBuffer(ctx, &sig_len, argv[0]); + if (!sig || sig_len != 64) return JS_ThrowTypeError(ctx, "invalid signature format"); + + // Parse the signature + if (!secp256k1_ecdsa_signature_parse_compact(g_secp256k1_context, &signature, sig)) { + return JS_ThrowTypeError(ctx, "invalid signature"); + } + + // Get public key from second argument + pubkey_data = JS_GetArrayBuffer(ctx, &pubkey_len, argv[1]); + if (!pubkey_data || pubkey_len != sizeof(secp256k1_pubkey)) { + return JS_ThrowTypeError(ctx, "invalid public key format"); + } + + // Copy the public key data + memcpy(&pubkey, pubkey_data, sizeof(secp256k1_pubkey)); + + // Get message hash from third argument + msg = JS_GetArrayBuffer(ctx, &msg_len, argv[2]); + if (!msg || msg_len != 32) return JS_ThrowTypeError(ctx, "message must be 32 bytes"); + + // Perform the verification + int result = secp256k1_ecdsa_verify(g_secp256k1_context, &signature, msg, &pubkey); -exit: - if (err) return JS_EXCEPTION; - return ret; + return JS_NewBool(ctx, result == 1); } static const JSCFunctionListEntry js_secp256k1_funcs[] = { JS_CFUNC_DEF("recover", 3, recover), + JS_CFUNC_DEF("serializePubkey", 2, serialize_pubkey), + JS_CFUNC_DEF("parsePubkey", 1, parse_pubkey), + JS_CFUNC_DEF("verify", 3, verify), }; static int js_secp256k1_init(JSContext *ctx, JSModuleDef *m) {