From d54bd8132df641122d7fb6860e4a62e26282b182 Mon Sep 17 00:00:00 2001 From: Lyndon Date: Wed, 8 Jan 2025 17:21:22 +0800 Subject: [PATCH] feat: add sha2-sha256, keccak256, ripemd160, blake2b bindings --- Makefile | 1 + src/conversion.h | 75 +++++++ src/hash_module.c | 446 ++++++++++++++++++++++++++++++++++++++ src/hash_module.h | 8 + src/qjs.c | 3 + src/ripemd160.h | 304 ++++++++++++++++++++++++++ src/sha256.h | 168 ++++++++++++++ test_hash.js | 1 + tests/module/Makefile | 1 + tests/module/test_hash.js | 360 ++++++++++++++++++++++++++++++ 10 files changed, 1367 insertions(+) create mode 100644 src/conversion.h create mode 100644 src/hash_module.c create mode 100644 src/hash_module.h create mode 100644 src/ripemd160.h create mode 100644 src/sha256.h create mode 100644 test_hash.js create mode 100644 tests/module/test_hash.js diff --git a/Makefile b/Makefile index 9261a74..43e0af9 100644 --- a/Makefile +++ b/Makefile @@ -109,6 +109,7 @@ build/ckb-js-vm: build/ckb-c-stdlib/impl.o \ build/secp256k1/precomputed_ecmult.o \ build/src/ckb_module.o \ build/src/secp256k1_module.o \ + build/src/hash_module.o \ build/src/qjs.o \ build/src/std_module.o \ deps/compiler-rt-builtins-riscv/build/libcompiler-rt.a diff --git a/src/conversion.h b/src/conversion.h new file mode 100644 index 0000000..c9cf570 --- /dev/null +++ b/src/conversion.h @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2014-2018 The Bitcoin Core developers + * Distributed under the MIT software license, see the accompanying + * file COPYING or http://www.opensource.org/licenses/mit-license.php. + * + * only used on little endian + */ + +#ifndef CONVERSION_H +#define CONVERSION_H + +#include +#include + +uint32_t static inline le32toh_(uint32_t x) { return x; } + +uint32_t static inline htole32_(uint32_t x) { return x; } + +uint32_t static inline be32toh_(uint32_t x) { return bswap_32(x); } + +uint32_t static inline htobe32_(uint32_t x) { return bswap_32(x); } + +uint64_t static inline le64toh_(uint64_t x) { return x; } + +uint64_t static inline htole64_(uint64_t x) { return x; } + +uint32_t static inline be64toh_(uint64_t x) { return bswap_64(x); } + +uint64_t static inline htobe64_(uint64_t x) { return bswap_64(x); } + +uint32_t static inline ReadLE32(const unsigned char* ptr) { + uint32_t x; + memcpy((char*)&x, ptr, 4); + return le32toh_(x); +} + +void static inline WriteLE32(unsigned char* ptr, uint32_t x) { + uint32_t v = htole32_(x); + memcpy(ptr, (char*)&v, 4); +} + +uint32_t static inline ReadBE32(const unsigned char* ptr) { + uint32_t x; + memcpy((char*)&x, ptr, 4); + return be32toh_(x); +} + +void static inline WriteBE32(unsigned char* ptr, uint32_t x) { + uint32_t v = htobe32_(x); + memcpy(ptr, (char*)&v, 4); +} + +void static inline WriteLE64(unsigned char* ptr, uint64_t x) { + uint64_t v = htole64_(x); + memcpy(ptr, (char*)&v, 8); +} + +uint64_t static inline ReadLE64(const unsigned char* ptr) { + uint64_t x; + memcpy((char*)&x, ptr, 8); + return le64toh_(x); +} + +void static inline WriteBE64(unsigned char* ptr, uint64_t x) { + uint64_t v = htobe64_(x); + memcpy(ptr, (char*)&v, 8); +} + +uint64_t static inline ReadBE64(const unsigned char* ptr) { + uint64_t x; + memcpy((char*)&x, ptr, 8); + return be64toh_(x); +} + +#endif diff --git a/src/hash_module.c b/src/hash_module.c new file mode 100644 index 0000000..236296e --- /dev/null +++ b/src/hash_module.c @@ -0,0 +1,446 @@ +#include +#include "cutils.h" +#include "hash_module.h" +#include "sha256.h" +#include "ckb_keccak256.h" +#include "blake2b.h" +#include "ripemd160.h" + +static int js_blake2b_init(blake2b_state *S, size_t outlen, char personal[BLAKE2B_PERSONALBYTES]) { + blake2b_param P[1]; + + if ((!outlen) || (outlen > BLAKE2B_OUTBYTES)) return -1; + + P->digest_length = (uint8_t)outlen; + P->key_length = 0; + P->fanout = 1; + P->depth = 1; + store32(&P->leaf_length, 0); + store32(&P->node_offset, 0); + store32(&P->xof_length, 0); + P->node_depth = 0; + P->inner_length = 0; + memset(P->reserved, 0, sizeof(P->reserved)); + memset(P->salt, 0, sizeof(P->salt)); + memset(P->personal, 0, sizeof(P->personal)); + for (int i = 0; i < BLAKE2B_PERSONALBYTES; ++i) { + (P->personal)[i] = personal[i]; + } + return blake2b_init_param(S, P); +} + +static void free_hash_context(JSRuntime *rt, void *opaque, void *ptr) { js_free_rt(rt, ptr); } + +static JSClassID js_sha256_class_id; + +static void js_sha256_finalizer(JSRuntime *rt, JSValue val) { + SHA256_CTX *hash = JS_GetOpaque(val, js_sha256_class_id); + if (hash) { + js_free_rt(rt, hash); + } +} + +static JSClassDef js_sha256_class = { + "Sha256", + .finalizer = js_sha256_finalizer, +}; + +// Constructor for Sha256 class +static JSValue js_sha256_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { + JSValue obj = JS_UNDEFINED; + JSValue proto; + SHA256_CTX *hash; + + // Create the object with proper prototype + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, js_sha256_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, js_sha256_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) goto fail; + + // Initialize hash context + hash = js_mallocz(ctx, sizeof(*hash)); + if (!hash) goto fail; + + sha256_init(hash); + JS_SetOpaque(obj, hash); + return obj; + +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +// Method definitions for Sha256 prototype +static JSValue js_sha256_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + size_t data_len; + uint8_t *data; + SHA256_CTX *hash; + + hash = JS_GetOpaque2(ctx, this_val, js_sha256_class_id); + if (!hash) return JS_EXCEPTION; + + data = JS_GetArrayBuffer(ctx, &data_len, argv[0]); + if (!data) return JS_ThrowTypeError(ctx, "invalid data"); + + sha256_update(hash, data, data_len); + return JS_UNDEFINED; +} + +static JSValue js_sha256_finalize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + SHA256_CTX *hash; + uint8_t *output; + + hash = JS_GetOpaque2(ctx, this_val, js_sha256_class_id); + if (!hash) return JS_EXCEPTION; + + output = js_malloc(ctx, SHA256_BLOCK_SIZE); + if (!output) return JS_ThrowOutOfMemory(ctx); + + sha256_final(hash, output); + return JS_NewArrayBuffer(ctx, output, SHA256_BLOCK_SIZE, free_hash_context, NULL, false); +} + +static const JSCFunctionListEntry js_sha256_proto_funcs[] = { + JS_CFUNC_DEF("write", 1, js_sha256_write), + JS_CFUNC_DEF("finalize", 0, js_sha256_finalize), +}; + +static JSClassID js_keccak256_class_id; + +static void js_keccak256_finalizer(JSRuntime *rt, JSValue val) { + SHA3_CTX *hash = JS_GetOpaque(val, js_keccak256_class_id); + if (hash) { + js_free_rt(rt, hash); + } +} + +static JSClassDef js_keccak256_class = { + "Keccak256", + .finalizer = js_keccak256_finalizer, +}; + +// Constructor for Keccak256 class +static JSValue js_keccak256_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { + JSValue obj = JS_UNDEFINED; + JSValue proto; + SHA3_CTX *hash; + + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, js_keccak256_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, js_keccak256_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) goto fail; + + hash = js_mallocz(ctx, sizeof(*hash)); + if (!hash) goto fail; + + keccak_init(hash); + JS_SetOpaque(obj, hash); + return obj; + +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +// Method definitions for Keccak256 prototype +static JSValue js_keccak256_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + size_t data_len; + uint8_t *data; + SHA3_CTX *hash; + + hash = JS_GetOpaque2(ctx, this_val, js_keccak256_class_id); + if (!hash) return JS_EXCEPTION; + + data = JS_GetArrayBuffer(ctx, &data_len, argv[0]); + if (!data) return JS_ThrowTypeError(ctx, "invalid data"); + + keccak_update(hash, data, data_len); + return JS_UNDEFINED; +} + +static JSValue js_keccak256_finalize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + SHA3_CTX *hash; + uint8_t *output; + const size_t KECCAK256_SIZE = 32; // 256 bits = 32 bytes + + hash = JS_GetOpaque2(ctx, this_val, js_keccak256_class_id); + if (!hash) return JS_EXCEPTION; + + output = js_malloc(ctx, KECCAK256_SIZE); + if (!output) return JS_ThrowOutOfMemory(ctx); + + keccak_final(hash, output); + return JS_NewArrayBuffer(ctx, output, KECCAK256_SIZE, free_hash_context, NULL, false); +} + +static const JSCFunctionListEntry js_keccak256_proto_funcs[] = { + JS_CFUNC_DEF("write", 1, js_keccak256_write), + JS_CFUNC_DEF("finalize", 0, js_keccak256_finalize), +}; + +static JSClassID js_blake2b_class_id; + +static void js_blake2b_finalizer(JSRuntime *rt, JSValue val) { + blake2b_state *hash = JS_GetOpaque(val, js_blake2b_class_id); + if (hash) { + js_free_rt(rt, hash); + } +} + +static JSClassDef js_blake2b_class = { + "Blake2b", + .finalizer = js_blake2b_finalizer, +}; + +// Constructor for Blake2b class +static JSValue js_blake2b_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { + JSValue obj = JS_UNDEFINED; + JSValue proto; + blake2b_state *hash; + const char *personal = NULL; + size_t personal_len; + + // Get personalization string from first argument + if (argc != 1) { + JS_ThrowTypeError(ctx, "must provide personal with size of 16"); + } + personal = JS_ToCStringLen(ctx, &personal_len, argv[0]); + if (!personal) goto fail; + if (personal_len != BLAKE2B_PERSONALBYTES) { + JS_FreeCString(ctx, personal); + JS_ThrowTypeError(ctx, "personal must be %d bytes", BLAKE2B_PERSONALBYTES); + goto fail; + } + + // Create the object with proper prototype + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, js_blake2b_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) { + if (personal) JS_FreeCString(ctx, personal); + goto fail; + } + } + obj = JS_NewObjectProtoClass(ctx, proto, js_blake2b_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) { + if (personal) JS_FreeCString(ctx, personal); + goto fail; + } + + // Initialize hash context + hash = js_mallocz(ctx, sizeof(*hash)); + if (!hash) { + if (personal) JS_FreeCString(ctx, personal); + goto fail; + } + + if (js_blake2b_init(hash, BLAKE2B_OUTBYTES, (char *)personal) < 0) { + js_free(ctx, hash); + if (personal) JS_FreeCString(ctx, personal); + goto fail; + } + + if (personal) JS_FreeCString(ctx, personal); + JS_SetOpaque(obj, hash); + return obj; + +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +// Method definitions for Blake2b prototype +static JSValue js_blake2b_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + size_t data_len; + uint8_t *data; + blake2b_state *hash; + + hash = JS_GetOpaque2(ctx, this_val, js_blake2b_class_id); + if (!hash) return JS_EXCEPTION; + + data = JS_GetArrayBuffer(ctx, &data_len, argv[0]); + if (!data) return JS_ThrowTypeError(ctx, "invalid data"); + + blake2b_update(hash, data, data_len); + return JS_UNDEFINED; +} + +static JSValue js_blake2b_finalize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + blake2b_state *hash; + uint8_t *output; + + hash = JS_GetOpaque2(ctx, this_val, js_blake2b_class_id); + if (!hash) return JS_EXCEPTION; + + output = js_malloc(ctx, BLAKE2B_OUTBYTES); + if (!output) return JS_ThrowOutOfMemory(ctx); + + blake2b_final(hash, output, BLAKE2B_OUTBYTES); + return JS_NewArrayBuffer(ctx, output, BLAKE2B_OUTBYTES, free_hash_context, NULL, false); +} + +static const JSCFunctionListEntry js_blake2b_proto_funcs[] = { + JS_CFUNC_DEF("write", 1, js_blake2b_write), + JS_CFUNC_DEF("finalize", 0, js_blake2b_finalize), +}; + +static JSClassID js_ripemd160_class_id; + +static void js_ripemd160_finalizer(JSRuntime *rt, JSValue val) { + ripemd160_state *hash = JS_GetOpaque(val, js_ripemd160_class_id); + if (hash) { + js_free_rt(rt, hash); + } +} + +static JSClassDef js_ripemd160_class = { + "Ripemd160", + .finalizer = js_ripemd160_finalizer, +}; + +// Constructor for Ripemd160 class +static JSValue js_ripemd160_ctor(JSContext *ctx, JSValueConst new_target, int argc, JSValueConst *argv) { + JSValue obj = JS_UNDEFINED; + JSValue proto; + ripemd160_state *hash; + + // Create the object with proper prototype + if (JS_IsUndefined(new_target)) { + proto = JS_GetClassProto(ctx, js_ripemd160_class_id); + } else { + proto = JS_GetPropertyStr(ctx, new_target, "prototype"); + if (JS_IsException(proto)) goto fail; + } + obj = JS_NewObjectProtoClass(ctx, proto, js_ripemd160_class_id); + JS_FreeValue(ctx, proto); + if (JS_IsException(obj)) goto fail; + + // Initialize hash context + hash = js_mallocz(ctx, sizeof(*hash)); + if (!hash) goto fail; + + ripemd160_init(hash); + JS_SetOpaque(obj, hash); + return obj; + +fail: + JS_FreeValue(ctx, obj); + return JS_EXCEPTION; +} + +// Method definitions for Ripemd160 prototype +static JSValue js_ripemd160_write(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + size_t data_len; + uint8_t *data; + ripemd160_state *hash; + + hash = JS_GetOpaque2(ctx, this_val, js_ripemd160_class_id); + if (!hash) return JS_EXCEPTION; + + data = JS_GetArrayBuffer(ctx, &data_len, argv[0]); + if (!data) return JS_ThrowTypeError(ctx, "invalid data"); + + ripemd160_update(hash, data, data_len); + return JS_UNDEFINED; +} + +static JSValue js_ripemd160_finalize(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv) { + ripemd160_state *hash; + uint8_t *output; + const size_t RIPEMD160_SIZE = 20; // 160 bits = 20 bytes + + hash = JS_GetOpaque2(ctx, this_val, js_ripemd160_class_id); + if (!hash) return JS_EXCEPTION; + + output = js_malloc(ctx, RIPEMD160_SIZE); + if (!output) return JS_ThrowOutOfMemory(ctx); + + ripemd160_finalize(hash, output); + return JS_NewArrayBuffer(ctx, output, RIPEMD160_SIZE, free_hash_context, NULL, false); +} + +static const JSCFunctionListEntry js_ripemd160_proto_funcs[] = { + JS_CFUNC_DEF("write", 1, js_ripemd160_write), + JS_CFUNC_DEF("finalize", 0, js_ripemd160_finalize), +}; + +static int js_hash_init(JSContext *ctx, JSModuleDef *m) { + JSValue proto, obj; + + // Initialize Sha256 class + JS_NewClassID(&js_sha256_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_sha256_class_id, &js_sha256_class); + + // Create and setup prototype + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_sha256_proto_funcs, countof(js_sha256_proto_funcs)); + + // Create constructor + obj = JS_NewCFunction2(ctx, js_sha256_ctor, "Sha256", 0, JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + JS_SetClassProto(ctx, js_sha256_class_id, proto); + + // Export the Sha256 constructor + JS_SetModuleExport(ctx, m, "Sha256", obj); + + // Initialize Keccak256 class + JS_NewClassID(&js_keccak256_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_keccak256_class_id, &js_keccak256_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_keccak256_proto_funcs, countof(js_keccak256_proto_funcs)); + obj = JS_NewCFunction2(ctx, js_keccak256_ctor, "Keccak256", 0, JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + JS_SetClassProto(ctx, js_keccak256_class_id, proto); + JS_SetModuleExport(ctx, m, "Keccak256", obj); + + // Initialize Blake2b class + JS_NewClassID(&js_blake2b_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_blake2b_class_id, &js_blake2b_class); + + // Create and setup prototype + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_blake2b_proto_funcs, countof(js_blake2b_proto_funcs)); + + // Create constructor + obj = JS_NewCFunction2(ctx, js_blake2b_ctor, "Blake2b", 0, JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + JS_SetClassProto(ctx, js_blake2b_class_id, proto); + + // Export the Blake2b constructor + JS_SetModuleExport(ctx, m, "Blake2b", obj); + + // Initialize Ripemd160 class + JS_NewClassID(&js_ripemd160_class_id); + JS_NewClass(JS_GetRuntime(ctx), js_ripemd160_class_id, &js_ripemd160_class); + proto = JS_NewObject(ctx); + JS_SetPropertyFunctionList(ctx, proto, js_ripemd160_proto_funcs, countof(js_ripemd160_proto_funcs)); + obj = JS_NewCFunction2(ctx, js_ripemd160_ctor, "Ripemd160", 0, JS_CFUNC_constructor, 0); + JS_SetConstructor(ctx, obj, proto); + JS_SetClassProto(ctx, js_ripemd160_class_id, proto); + JS_SetModuleExport(ctx, m, "Ripemd160", obj); + + return 0; +} + +int js_init_module_hash(JSContext *ctx) { + JSModuleDef *m; + m = JS_NewCModule(ctx, "hash", js_hash_init); + if (!m) return -1; + JS_AddModuleExport(ctx, m, "Sha256"); + JS_AddModuleExport(ctx, m, "Keccak256"); + JS_AddModuleExport(ctx, m, "Blake2b"); + JS_AddModuleExport(ctx, m, "Ripemd160"); + return 0; +} diff --git a/src/hash_module.h b/src/hash_module.h new file mode 100644 index 0000000..1e4c0df --- /dev/null +++ b/src/hash_module.h @@ -0,0 +1,8 @@ +#ifndef HASH_MODULE_H +#define HASH_MODULE_H + +#include + +int js_init_module_hash(JSContext *ctx); + +#endif /* HASH_MODULE_H */ diff --git a/src/qjs.c b/src/qjs.c index 2b5e7ed..4498d46 100644 --- a/src/qjs.c +++ b/src/qjs.c @@ -33,6 +33,7 @@ #include "std_module.h" #include "ckb_module.h" #include "secp256k1_module.h" +#include "hash_module.h" #include "ckb_exec.h" #include "cmdopt.h" #include "qjs.h" @@ -367,6 +368,8 @@ int main(int argc, const char **argv) { CHECK(err); err = js_init_module_secp256k1(ctx); CHECK(err); + err = js_init_module_hash(ctx); + CHECK(err); bool c_bool = cmdopt_has(co, "c"); const char *e_data = cmdopt_get(co, "e"); diff --git a/src/ripemd160.h b/src/ripemd160.h new file mode 100644 index 0000000..d034dc8 --- /dev/null +++ b/src/ripemd160.h @@ -0,0 +1,304 @@ +// Copyright (c) 2014-2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef RIPEMD160_H +#define RIPEMD160_H + +#include +#include +#include + +#include "conversion.h" + +typedef struct ripemd160_state__ { + uint32_t s[5]; + unsigned char buf[64]; + uint64_t bytes; +} ripemd160_state; + +void ripemd160_init(ripemd160_state* S); +void ripemd160_update(ripemd160_state* S, const unsigned char* data, size_t len); +void ripemd160_finalize(ripemd160_state* S, unsigned char hash[20]); +void ripemd160_reset(ripemd160_state* S); + +static uint32_t inline f1(uint32_t x, uint32_t y, uint32_t z) { return x ^ y ^ z; } +uint32_t static inline f2(uint32_t x, uint32_t y, uint32_t z) { return (x & y) | (~x & z); } +static uint32_t inline f3(uint32_t x, uint32_t y, uint32_t z) { return (x | ~y) ^ z; } +static uint32_t inline f4(uint32_t x, uint32_t y, uint32_t z) { return (x & z) | (y & ~z); } +static uint32_t inline f5(uint32_t x, uint32_t y, uint32_t z) { return x ^ (y | ~z); } + +static uint32_t inline rol(uint32_t x, int i) { return (x << i) | (x >> (32 - i)); } + +static void inline ripemd160_round(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t f, uint32_t x, + uint32_t k, int r) { + *a = rol(*a + f + x + k, r) + e; + *c = rol(*c, 10); +} + +static void inline R11(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f1(b, *c, d), x, 0, r); +} +static void inline R21(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f2(b, *c, d), x, 0x5A827999ul, r); +} +static void inline R31(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f3(b, *c, d), x, 0x6ED9EBA1ul, r); +} +static void inline R41(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f4(b, *c, d), x, 0x8F1BBCDCul, r); +} +static void inline R51(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f5(b, *c, d), x, 0xA953FD4Eul, r); +} + +static void inline R12(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f5(b, *c, d), x, 0x50A28BE6ul, r); +} +static void inline R22(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f4(b, *c, d), x, 0x5C4DD124ul, r); +} +static void inline R32(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f3(b, *c, d), x, 0x6D703EF3ul, r); +} +static void inline R42(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f2(b, *c, d), x, 0x7A6D76E9ul, r); +} +static void inline R52(uint32_t* a, uint32_t b, uint32_t* c, uint32_t d, uint32_t e, uint32_t x, int r) { + ripemd160_round(a, b, c, d, e, f1(b, *c, d), x, 0, r); +} + +/** Perform a RIPEMD-160 transformation, processing a 64-byte chunk. */ +static void ripemd160_transform(uint32_t* s, const unsigned char* chunk) { + uint32_t a1 = s[0], b1 = s[1], c1 = s[2], d1 = s[3], e1 = s[4]; + uint32_t a2 = a1, b2 = b1, c2 = c1, d2 = d1, e2 = e1; + uint32_t w0 = ReadLE32(chunk + 0), w1 = ReadLE32(chunk + 4), w2 = ReadLE32(chunk + 8), w3 = ReadLE32(chunk + 12); + uint32_t w4 = ReadLE32(chunk + 16), w5 = ReadLE32(chunk + 20), w6 = ReadLE32(chunk + 24), w7 = ReadLE32(chunk + 28); + uint32_t w8 = ReadLE32(chunk + 32), w9 = ReadLE32(chunk + 36), w10 = ReadLE32(chunk + 40), + w11 = ReadLE32(chunk + 44); + uint32_t w12 = ReadLE32(chunk + 48), w13 = ReadLE32(chunk + 52), w14 = ReadLE32(chunk + 56), + w15 = ReadLE32(chunk + 60); + + R11(&a1, b1, &c1, d1, e1, w0, 11); + R12(&a2, b2, &c2, d2, e2, w5, 8); + R11(&e1, a1, &b1, c1, d1, w1, 14); + R12(&e2, a2, &b2, c2, d2, w14, 9); + R11(&d1, e1, &a1, b1, c1, w2, 15); + R12(&d2, e2, &a2, b2, c2, w7, 9); + R11(&c1, d1, &e1, a1, b1, w3, 12); + R12(&c2, d2, &e2, a2, b2, w0, 11); + R11(&b1, c1, &d1, e1, a1, w4, 5); + R12(&b2, c2, &d2, e2, a2, w9, 13); + R11(&a1, b1, &c1, d1, e1, w5, 8); + R12(&a2, b2, &c2, d2, e2, w2, 15); + R11(&e1, a1, &b1, c1, d1, w6, 7); + R12(&e2, a2, &b2, c2, d2, w11, 15); + R11(&d1, e1, &a1, b1, c1, w7, 9); + R12(&d2, e2, &a2, b2, c2, w4, 5); + R11(&c1, d1, &e1, a1, b1, w8, 11); + R12(&c2, d2, &e2, a2, b2, w13, 7); + R11(&b1, c1, &d1, e1, a1, w9, 13); + R12(&b2, c2, &d2, e2, a2, w6, 7); + R11(&a1, b1, &c1, d1, e1, w10, 14); + R12(&a2, b2, &c2, d2, e2, w15, 8); + R11(&e1, a1, &b1, c1, d1, w11, 15); + R12(&e2, a2, &b2, c2, d2, w8, 11); + R11(&d1, e1, &a1, b1, c1, w12, 6); + R12(&d2, e2, &a2, b2, c2, w1, 14); + R11(&c1, d1, &e1, a1, b1, w13, 7); + R12(&c2, d2, &e2, a2, b2, w10, 14); + R11(&b1, c1, &d1, e1, a1, w14, 9); + R12(&b2, c2, &d2, e2, a2, w3, 12); + R11(&a1, b1, &c1, d1, e1, w15, 8); + R12(&a2, b2, &c2, d2, e2, w12, 6); + + R21(&e1, a1, &b1, c1, d1, w7, 7); + R22(&e2, a2, &b2, c2, d2, w6, 9); + R21(&d1, e1, &a1, b1, c1, w4, 6); + R22(&d2, e2, &a2, b2, c2, w11, 13); + R21(&c1, d1, &e1, a1, b1, w13, 8); + R22(&c2, d2, &e2, a2, b2, w3, 15); + R21(&b1, c1, &d1, e1, a1, w1, 13); + R22(&b2, c2, &d2, e2, a2, w7, 7); + R21(&a1, b1, &c1, d1, e1, w10, 11); + R22(&a2, b2, &c2, d2, e2, w0, 12); + R21(&e1, a1, &b1, c1, d1, w6, 9); + R22(&e2, a2, &b2, c2, d2, w13, 8); + R21(&d1, e1, &a1, b1, c1, w15, 7); + R22(&d2, e2, &a2, b2, c2, w5, 9); + R21(&c1, d1, &e1, a1, b1, w3, 15); + R22(&c2, d2, &e2, a2, b2, w10, 11); + R21(&b1, c1, &d1, e1, a1, w12, 7); + R22(&b2, c2, &d2, e2, a2, w14, 7); + R21(&a1, b1, &c1, d1, e1, w0, 12); + R22(&a2, b2, &c2, d2, e2, w15, 7); + R21(&e1, a1, &b1, c1, d1, w9, 15); + R22(&e2, a2, &b2, c2, d2, w8, 12); + R21(&d1, e1, &a1, b1, c1, w5, 9); + R22(&d2, e2, &a2, b2, c2, w12, 7); + R21(&c1, d1, &e1, a1, b1, w2, 11); + R22(&c2, d2, &e2, a2, b2, w4, 6); + R21(&b1, c1, &d1, e1, a1, w14, 7); + R22(&b2, c2, &d2, e2, a2, w9, 15); + R21(&a1, b1, &c1, d1, e1, w11, 13); + R22(&a2, b2, &c2, d2, e2, w1, 13); + R21(&e1, a1, &b1, c1, d1, w8, 12); + R22(&e2, a2, &b2, c2, d2, w2, 11); + + R31(&d1, e1, &a1, b1, c1, w3, 11); + R32(&d2, e2, &a2, b2, c2, w15, 9); + R31(&c1, d1, &e1, a1, b1, w10, 13); + R32(&c2, d2, &e2, a2, b2, w5, 7); + R31(&b1, c1, &d1, e1, a1, w14, 6); + R32(&b2, c2, &d2, e2, a2, w1, 15); + R31(&a1, b1, &c1, d1, e1, w4, 7); + R32(&a2, b2, &c2, d2, e2, w3, 11); + R31(&e1, a1, &b1, c1, d1, w9, 14); + R32(&e2, a2, &b2, c2, d2, w7, 8); + R31(&d1, e1, &a1, b1, c1, w15, 9); + R32(&d2, e2, &a2, b2, c2, w14, 6); + R31(&c1, d1, &e1, a1, b1, w8, 13); + R32(&c2, d2, &e2, a2, b2, w6, 6); + R31(&b1, c1, &d1, e1, a1, w1, 15); + R32(&b2, c2, &d2, e2, a2, w9, 14); + R31(&a1, b1, &c1, d1, e1, w2, 14); + R32(&a2, b2, &c2, d2, e2, w11, 12); + R31(&e1, a1, &b1, c1, d1, w7, 8); + R32(&e2, a2, &b2, c2, d2, w8, 13); + R31(&d1, e1, &a1, b1, c1, w0, 13); + R32(&d2, e2, &a2, b2, c2, w12, 5); + R31(&c1, d1, &e1, a1, b1, w6, 6); + R32(&c2, d2, &e2, a2, b2, w2, 14); + R31(&b1, c1, &d1, e1, a1, w13, 5); + R32(&b2, c2, &d2, e2, a2, w10, 13); + R31(&a1, b1, &c1, d1, e1, w11, 12); + R32(&a2, b2, &c2, d2, e2, w0, 13); + R31(&e1, a1, &b1, c1, d1, w5, 7); + R32(&e2, a2, &b2, c2, d2, w4, 7); + R31(&d1, e1, &a1, b1, c1, w12, 5); + R32(&d2, e2, &a2, b2, c2, w13, 5); + + R41(&c1, d1, &e1, a1, b1, w1, 11); + R42(&c2, d2, &e2, a2, b2, w8, 15); + R41(&b1, c1, &d1, e1, a1, w9, 12); + R42(&b2, c2, &d2, e2, a2, w6, 5); + R41(&a1, b1, &c1, d1, e1, w11, 14); + R42(&a2, b2, &c2, d2, e2, w4, 8); + R41(&e1, a1, &b1, c1, d1, w10, 15); + R42(&e2, a2, &b2, c2, d2, w1, 11); + R41(&d1, e1, &a1, b1, c1, w0, 14); + R42(&d2, e2, &a2, b2, c2, w3, 14); + R41(&c1, d1, &e1, a1, b1, w8, 15); + R42(&c2, d2, &e2, a2, b2, w11, 14); + R41(&b1, c1, &d1, e1, a1, w12, 9); + R42(&b2, c2, &d2, e2, a2, w15, 6); + R41(&a1, b1, &c1, d1, e1, w4, 8); + R42(&a2, b2, &c2, d2, e2, w0, 14); + R41(&e1, a1, &b1, c1, d1, w13, 9); + R42(&e2, a2, &b2, c2, d2, w5, 6); + R41(&d1, e1, &a1, b1, c1, w3, 14); + R42(&d2, e2, &a2, b2, c2, w12, 9); + R41(&c1, d1, &e1, a1, b1, w7, 5); + R42(&c2, d2, &e2, a2, b2, w2, 12); + R41(&b1, c1, &d1, e1, a1, w15, 6); + R42(&b2, c2, &d2, e2, a2, w13, 9); + R41(&a1, b1, &c1, d1, e1, w14, 8); + R42(&a2, b2, &c2, d2, e2, w9, 12); + R41(&e1, a1, &b1, c1, d1, w5, 6); + R42(&e2, a2, &b2, c2, d2, w7, 5); + R41(&d1, e1, &a1, b1, c1, w6, 5); + R42(&d2, e2, &a2, b2, c2, w10, 15); + R41(&c1, d1, &e1, a1, b1, w2, 12); + R42(&c2, d2, &e2, a2, b2, w14, 8); + + R51(&b1, c1, &d1, e1, a1, w4, 9); + R52(&b2, c2, &d2, e2, a2, w12, 8); + R51(&a1, b1, &c1, d1, e1, w0, 15); + R52(&a2, b2, &c2, d2, e2, w15, 5); + R51(&e1, a1, &b1, c1, d1, w5, 5); + R52(&e2, a2, &b2, c2, d2, w10, 12); + R51(&d1, e1, &a1, b1, c1, w9, 11); + R52(&d2, e2, &a2, b2, c2, w4, 9); + R51(&c1, d1, &e1, a1, b1, w7, 6); + R52(&c2, d2, &e2, a2, b2, w1, 12); + R51(&b1, c1, &d1, e1, a1, w12, 8); + R52(&b2, c2, &d2, e2, a2, w5, 5); + R51(&a1, b1, &c1, d1, e1, w2, 13); + R52(&a2, b2, &c2, d2, e2, w8, 14); + R51(&e1, a1, &b1, c1, d1, w10, 12); + R52(&e2, a2, &b2, c2, d2, w7, 6); + R51(&d1, e1, &a1, b1, c1, w14, 5); + R52(&d2, e2, &a2, b2, c2, w6, 8); + R51(&c1, d1, &e1, a1, b1, w1, 12); + R52(&c2, d2, &e2, a2, b2, w2, 13); + R51(&b1, c1, &d1, e1, a1, w3, 13); + R52(&b2, c2, &d2, e2, a2, w13, 6); + R51(&a1, b1, &c1, d1, e1, w8, 14); + R52(&a2, b2, &c2, d2, e2, w14, 5); + R51(&e1, a1, &b1, c1, d1, w11, 11); + R52(&e2, a2, &b2, c2, d2, w0, 15); + R51(&d1, e1, &a1, b1, c1, w6, 8); + R52(&d2, e2, &a2, b2, c2, w3, 13); + R51(&c1, d1, &e1, a1, b1, w15, 5); + R52(&c2, d2, &e2, a2, b2, w9, 11); + R51(&b1, c1, &d1, e1, a1, w13, 6); + R52(&b2, c2, &d2, e2, a2, w11, 11); + + uint32_t t = s[0]; + s[0] = s[1] + c1 + d2; + s[1] = s[2] + d1 + e2; + s[2] = s[3] + e1 + a2; + s[3] = s[4] + a1 + b2; + s[4] = t + b1 + c2; +} + +void inline ripemd160_init(ripemd160_state* S) { + memset(S, 0, sizeof(ripemd160_state)); + S->s[0] = 0x67452301ul; + S->s[1] = 0xEFCDAB89ul; + S->s[2] = 0x98BADCFEul; + S->s[3] = 0x10325476ul; + S->s[4] = 0xC3D2E1F0ul; + S->bytes = 0; +} + +void ripemd160_update(ripemd160_state* S, const unsigned char* data, size_t len) { + const unsigned char* end = data + len; + size_t bufsize = S->bytes % 64; + if (bufsize && bufsize + len >= 64) { + // Fill the buffer, and process it. + memcpy(S->buf + bufsize, data, 64 - bufsize); + S->bytes += 64 - bufsize; + data += 64 - bufsize; + ripemd160_transform(S->s, S->buf); + bufsize = 0; + } + while (end - data >= 64) { + // Process full chunks directly from the source. + ripemd160_transform(S->s, data); + S->bytes += 64; + data += 64; + } + if (end > data) { + // Fill the buffer with what remains. + memcpy(S->buf + bufsize, data, end - data); + S->bytes += end - data; + } +} + +void ripemd160_finalize(ripemd160_state* S, unsigned char hash[20]) { + static const unsigned char pad[64] = {0x80}; + unsigned char sizedesc[8]; + WriteLE64(sizedesc, S->bytes << 3); + ripemd160_update(S, pad, 1 + ((119 - (S->bytes % 64)) % 64)); + ripemd160_update(S, sizedesc, 8); + WriteLE32(hash, S->s[0]); + WriteLE32(hash + 4, S->s[1]); + WriteLE32(hash + 8, S->s[2]); + WriteLE32(hash + 12, S->s[3]); + WriteLE32(hash + 16, S->s[4]); +} + +void ripemd160_reset(ripemd160_state* S) { ripemd160_init(S); } + +#endif diff --git a/src/sha256.h b/src/sha256.h new file mode 100644 index 0000000..0be1f1b --- /dev/null +++ b/src/sha256.h @@ -0,0 +1,168 @@ +/********************************************************************* + * Filename: sha256.h + * Author: Brad Conte (brad AT bradconte.com) + * Copyright: + * Disclaimer: This code is presented "as is" without any guarantees. + * Details: Defines the API for the corresponding SHA1 implementation. + *********************************************************************/ + +#ifndef SHA256_H +#define SHA256_H + +/*************************** HEADER FILES ***************************/ +#include +#include +#include + +/****************************** MACROS ******************************/ +#define SHA256_BLOCK_SIZE 32 // SHA256 outputs a 32 byte digest + +/**************************** DATA TYPES ****************************/ +typedef unsigned char BYTE; // 8-bit byte +typedef unsigned int WORD; // 32-bit word, change to "long" for 16-bit machines + +typedef struct { + BYTE data[64]; + WORD datalen; + unsigned long long bitlen; + WORD state[8]; +} SHA256_CTX; + +/*********************** FUNCTION DECLARATIONS **********************/ +void sha256_init(SHA256_CTX *ctx); +void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len); +void sha256_final(SHA256_CTX *ctx, BYTE hash[]); + +/****************************** MACROS ******************************/ +#define ROTLEFT(a, b) (((a) << (b)) | ((a) >> (32 - (b)))) +#define ROTRIGHT(a, b) (((a) >> (b)) | ((a) << (32 - (b)))) + +#define CH(x, y, z) (((x) & (y)) ^ (~(x) & (z))) +#define MAJ(x, y, z) (((x) & (y)) ^ ((x) & (z)) ^ ((y) & (z))) +#define EP0(x) (ROTRIGHT(x, 2) ^ ROTRIGHT(x, 13) ^ ROTRIGHT(x, 22)) +#define EP1(x) (ROTRIGHT(x, 6) ^ ROTRIGHT(x, 11) ^ ROTRIGHT(x, 25)) +#define SIG0(x) (ROTRIGHT(x, 7) ^ ROTRIGHT(x, 18) ^ ((x) >> 3)) +#define SIG1(x) (ROTRIGHT(x, 17) ^ ROTRIGHT(x, 19) ^ ((x) >> 10)) + +/**************************** VARIABLES *****************************/ +static const WORD k[64] = { + 0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5, + 0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174, + 0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da, + 0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967, + 0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85, + 0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070, + 0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3, + 0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2}; + +/*********************** FUNCTION DEFINITIONS ***********************/ +void sha256_transform(SHA256_CTX *ctx, const BYTE data[]) { + WORD a, b, c, d, e, f, g, h, i, j, t1, t2, m[64]; + + for (i = 0, j = 0; i < 16; ++i, j += 4) + m[i] = (data[j] << 24) | (data[j + 1] << 16) | (data[j + 2] << 8) | (data[j + 3]); + for (; i < 64; ++i) m[i] = SIG1(m[i - 2]) + m[i - 7] + SIG0(m[i - 15]) + m[i - 16]; + + a = ctx->state[0]; + b = ctx->state[1]; + c = ctx->state[2]; + d = ctx->state[3]; + e = ctx->state[4]; + f = ctx->state[5]; + g = ctx->state[6]; + h = ctx->state[7]; + + for (i = 0; i < 64; ++i) { + t1 = h + EP1(e) + CH(e, f, g) + k[i] + m[i]; + t2 = EP0(a) + MAJ(a, b, c); + h = g; + g = f; + f = e; + e = d + t1; + d = c; + c = b; + b = a; + a = t1 + t2; + } + + ctx->state[0] += a; + ctx->state[1] += b; + ctx->state[2] += c; + ctx->state[3] += d; + ctx->state[4] += e; + ctx->state[5] += f; + ctx->state[6] += g; + ctx->state[7] += h; +} + +void sha256_init(SHA256_CTX *ctx) { + ctx->datalen = 0; + ctx->bitlen = 0; + ctx->state[0] = 0x6a09e667; + ctx->state[1] = 0xbb67ae85; + ctx->state[2] = 0x3c6ef372; + ctx->state[3] = 0xa54ff53a; + ctx->state[4] = 0x510e527f; + ctx->state[5] = 0x9b05688c; + ctx->state[6] = 0x1f83d9ab; + ctx->state[7] = 0x5be0cd19; +} + +void sha256_update(SHA256_CTX *ctx, const BYTE data[], size_t len) { + WORD i; + + for (i = 0; i < len; ++i) { + ctx->data[ctx->datalen] = data[i]; + ctx->datalen++; + if (ctx->datalen == 64) { + sha256_transform(ctx, ctx->data); + ctx->bitlen += 512; + ctx->datalen = 0; + } + } +} + +void sha256_final(SHA256_CTX *ctx, BYTE hash[]) { + WORD i; + + i = ctx->datalen; + + // Pad whatever data is left in the buffer. + if (ctx->datalen < 56) { + ctx->data[i++] = 0x80; + while (i < 56) ctx->data[i++] = 0x00; + } else { + ctx->data[i++] = 0x80; + while (i < 64) ctx->data[i++] = 0x00; + sha256_transform(ctx, ctx->data); + memset(ctx->data, 0, 56); + } + + // Append to the padding the total message's length in bits and transform. + ctx->bitlen += ctx->datalen * 8; + ctx->data[63] = ctx->bitlen; + ctx->data[62] = ctx->bitlen >> 8; + ctx->data[61] = ctx->bitlen >> 16; + ctx->data[60] = ctx->bitlen >> 24; + ctx->data[59] = ctx->bitlen >> 32; + ctx->data[58] = ctx->bitlen >> 40; + ctx->data[57] = ctx->bitlen >> 48; + ctx->data[56] = ctx->bitlen >> 56; + sha256_transform(ctx, ctx->data); + + // Since this implementation uses little endian byte ordering and SHA uses big + // endian, reverse all the bytes when copying the final state to the output + // hash. + for (i = 0; i < 4; ++i) { + hash[i] = (ctx->state[0] >> (24 - i * 8)) & 0x000000ff; + hash[i + 4] = (ctx->state[1] >> (24 - i * 8)) & 0x000000ff; + hash[i + 8] = (ctx->state[2] >> (24 - i * 8)) & 0x000000ff; + hash[i + 12] = (ctx->state[3] >> (24 - i * 8)) & 0x000000ff; + hash[i + 16] = (ctx->state[4] >> (24 - i * 8)) & 0x000000ff; + hash[i + 20] = (ctx->state[5] >> (24 - i * 8)) & 0x000000ff; + hash[i + 24] = (ctx->state[6] >> (24 - i * 8)) & 0x000000ff; + hash[i + 28] = (ctx->state[7] >> (24 - i * 8)) & 0x000000ff; + } +} + +#endif // SHA256_H diff --git a/test_hash.js b/test_hash.js new file mode 100644 index 0000000..0519ecb --- /dev/null +++ b/test_hash.js @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/module/Makefile b/tests/module/Makefile index 64bbcbb..007e56b 100644 --- a/tests/module/Makefile +++ b/tests/module/Makefile @@ -17,3 +17,4 @@ endef all: $(call run,test_secp256k1.js) + $(call debug,test_hash.js) \ No newline at end of file diff --git a/tests/module/test_hash.js b/tests/module/test_hash.js new file mode 100644 index 0000000..bae89a8 --- /dev/null +++ b/tests/module/test_hash.js @@ -0,0 +1,360 @@ +import * as hash from 'hash'; +import * as ckb from 'ckb'; + +// Add global constant +const CKB_DEFAULT_HASH = 'ckb-default-hash'; + +function hexStringToUint8Array(hexString) { + hexString = hexString.replace(/[^0-9A-Fa-f]/g, ''); + const bytes = new Uint8Array(hexString.length / 2); + for (let i = 0; i < hexString.length; i += 2) { + bytes[i / 2] = parseInt(hexString.substr(i, 2), 16); + } + return bytes; +} + +function arrayBufferToHexString(buffer) { + return Array.from(new Uint8Array(buffer)) + .map(b => b.toString(16).padStart(2, '0')) + .join(''); +} + +function stringToUint8Array(str) { + const arr = new Uint8Array(str.length); + for (let i = 0; i < str.length; i++) { + arr[i] = str.charCodeAt(i); + } + return arr; +} + +function test_sha2_sha256_empty_string() { + // SHA256 of empty string + const expected = + 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855'; + + const sha256 = new hash.Sha256(); + const start = ckb.current_cycles(); + sha256.write(new Uint8Array(0).buffer); + const result = sha256.finalize(); + const end = ckb.current_cycles(); + console.log(`empty string hash cycles: ${end - start}`); + + console.assert( + arrayBufferToHexString(result) === expected, + 'Empty string hash failed'); + console.log('test_sha2_sha256_empty_string ok'); +} + +function test_keccak256_empty_string() { + const expected = + 'c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470'; + + const keccak256 = new hash.Keccak256(); + const start = ckb.current_cycles(); + keccak256.write(new Uint8Array(0).buffer); + const result = keccak256.finalize(); + const end = ckb.current_cycles(); + console.log(`empty string hash cycles: ${end - start}`); + + console.assert( + arrayBufferToHexString(result) === expected, + 'Empty string hash failed'); + console.log('test_keccak256_empty_string ok'); +} + + +function test_sha2_sha256_basic_string() { + // SHA256 of "hello" + const input = hexStringToUint8Array('68656c6c6f'); // "hello" in hex + const expected = + '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'; + + const sha256 = new hash.Sha256(); + const start = ckb.current_cycles(); + sha256.write(input.buffer); + const result = sha256.finalize(); + const end = ckb.current_cycles(); + console.log(`basic string hash cycles: ${end - start}`); + + console.assert( + arrayBufferToHexString(result) === expected, + 'Basic string hash failed'); + console.log('test_sha2_sha256_basic_string ok'); +} + +function test_sha2_sha256_multiple_updates() { + // Testing multiple write operations + const input1 = hexStringToUint8Array('68656c'); // "hel" + const input2 = hexStringToUint8Array('6c6f'); // "lo" + const expected = + '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'; + + const sha256 = new hash.Sha256(); + const start = ckb.current_cycles(); + sha256.write(input1.buffer); + sha256.write(input2.buffer); + const result = sha256.finalize(); + const end = ckb.current_cycles(); + console.log(`multiple updates hash cycles: ${end - start}`); + + console.assert( + arrayBufferToHexString(result) === expected, + 'Multiple updates hash failed'); + console.log('test_sha2_sha256_multiple_updates ok'); +} + +function test_sha2_sha256_long_string() { + // Testing with a longer string + const input = 'a'.repeat(1000); + const inputBytes = stringToUint8Array(input); + const expected = + '41edece42d63e8d9bf515a9ba6932e1c20cbc9f5a5d134645adb5db1b9737ea3'; + + const sha256 = new hash.Sha256(); + const start = ckb.current_cycles(); + sha256.write(inputBytes.buffer); + const result = sha256.finalize(); + const end = ckb.current_cycles(); + console.log(`long string hash cycles: ${end - start}`); + console.assert( + arrayBufferToHexString(result) === expected, 'Long string hash failed'); + console.log('test_sha2_sha256_long_string ok'); +} + +function test_sha2_sha256_error_handling() { + let success = false; + try { + const sha256 = new hash.Sha256(); + sha256.write('invalid input'); // Should throw type error + } catch (e) { + success = true; + } + console.assert(success, 'Error handling test failed'); + console.log('test_sha2_sha256_error_handling ok'); +} + +function test_keccak256_basic_string() { + // Keccak256 of "hello" + const input = hexStringToUint8Array('68656c6c6f'); // "hello" in hex + const expected = + '1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8'; + + const keccak256 = new hash.Keccak256(); + const start = ckb.current_cycles(); + keccak256.write(input.buffer); + const result = keccak256.finalize(); + const end = ckb.current_cycles(); + console.log(`basic string hash cycles: ${end - start}`); + + console.assert( + arrayBufferToHexString(result) === expected, + 'Basic string hash failed'); + console.log('test_keccak256_basic_string ok'); +} + +function test_keccak256_multiple_updates() { + // Testing multiple write operations + const input1 = hexStringToUint8Array('68656c'); // "hel" + const input2 = hexStringToUint8Array('6c6f'); // "lo" + const expected = + '1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8'; + + const keccak256 = new hash.Keccak256(); + const start = ckb.current_cycles(); + keccak256.write(input1.buffer); + keccak256.write(input2.buffer); + const result = keccak256.finalize(); + const end = ckb.current_cycles(); + console.log(`multiple updates hash cycles: ${end - start}`); + + console.assert( + arrayBufferToHexString(result) === expected, + 'Multiple updates hash failed'); + console.log('test_keccak256_multiple_updates ok'); +} + +function test_keccak256_long_string() { + // Testing with a longer string + const input = 'a'.repeat(1000); + const inputBytes = stringToUint8Array(input); + const expected = + 'b6a4ac1f51884d71f30fa397a5e155de3099e11fc0edef5d08b646e621e19de9'; + + const keccak256 = new hash.Keccak256(); + const start = ckb.current_cycles(); + keccak256.write(inputBytes.buffer); + const result = keccak256.finalize(); + const end = ckb.current_cycles(); + console.log(`long string hash cycles: ${end - start}`); + console.assert( + arrayBufferToHexString(result) === expected, 'Long string hash failed'); + console.log('test_keccak256_long_string ok'); +} + +function test_blake2b_empty_string() { + // Blake2b of empty string with ckb-default-hash personalization + const expected = + '8e5e657ab293b4f6146feed495bf87c4c3c5e0cfca6aef78f924311866ea277bf359afae4a763af955e23abdad3f9c941c9e4a0a795c73d8b205679ab68eb294'; + + const blake2b = new hash.Blake2b(CKB_DEFAULT_HASH); + const start = ckb.current_cycles(); + blake2b.write(new Uint8Array(0).buffer); + const result = blake2b.finalize(); + const end = ckb.current_cycles(); + console.log(`empty string hash cycles: ${end - start}`); + console.assert( + arrayBufferToHexString(result) === expected, + 'Empty string hash failed'); + console.log('test_blake2b_empty_string ok'); +} + +function test_blake2b_basic_string() { + // Blake2b of "hello" + const input = hexStringToUint8Array('68656c6c6f'); // "hello" in hex + const expected = + 'a1e60e2fbb09f4f071f4e3cc30791fcdd694bfda60223c5b3912ae3d762a6ba59c9e90e9fd185c10eb545a4ca86a9bdc72539d5160576707a43760f4b50013ba'; + + const blake2b = new hash.Blake2b(CKB_DEFAULT_HASH); + const start = ckb.current_cycles(); + blake2b.write(input.buffer); + const result = blake2b.finalize(); + const end = ckb.current_cycles(); + console.log(`basic string hash cycles: ${end - start}`); + console.assert( + arrayBufferToHexString(result) === expected, + 'Basic string hash failed'); + console.log('test_blake2b_basic_string ok'); +} + +function test_blake2b_multiple_updates() { + // Testing multiple write operations + const input1 = hexStringToUint8Array('68656c'); // "hel" + const input2 = hexStringToUint8Array('6c6f'); // "lo" + const expected = + 'a1e60e2fbb09f4f071f4e3cc30791fcdd694bfda60223c5b3912ae3d762a6ba59c9e90e9fd185c10eb545a4ca86a9bdc72539d5160576707a43760f4b50013ba'; + + const blake2b = new hash.Blake2b(CKB_DEFAULT_HASH); + const start = ckb.current_cycles(); + blake2b.write(input1.buffer); + blake2b.write(input2.buffer); + const result = blake2b.finalize(); + const end = ckb.current_cycles(); + console.log(`multiple updates hash cycles: ${end - start}`); + console.assert( + arrayBufferToHexString(result) === expected, + 'Multiple updates hash failed'); + console.log('test_blake2b_multiple_updates ok'); +} + +function test_blake2b_long_string() { + // Testing with a longer string + const input = 'a'.repeat(1000); + const inputBytes = stringToUint8Array(input); + const expected = + 'e2bc748623468948e5483c45f6250557a672288edf3677535502e83f574f4d90aa296599678a010b7d5f1b0eb7249083f7294e6b80fea1351ca042dd10ddd7d4'; + + const blake2b = new hash.Blake2b(CKB_DEFAULT_HASH); + const start = ckb.current_cycles(); + blake2b.write(inputBytes.buffer); + const result = blake2b.finalize(); + const end = ckb.current_cycles(); + console.log(`long string hash cycles: ${end - start}`); + console.assert( + arrayBufferToHexString(result) === expected, 'Long string hash failed'); + console.log('test_blake2b_long_string ok'); +} + +function test_ripemd160_empty_string() { + // RIPEMD160 of empty string + const expected = '9c1185a5c5e9fc54612808977ee8f548b2258d31'; + + const ripemd160 = new hash.Ripemd160(); + const start = ckb.current_cycles(); + ripemd160.write(new Uint8Array(0).buffer); + const result = ripemd160.finalize(); + const end = ckb.current_cycles(); + console.log(`empty string hash cycles: ${end - start}`); + console.log("result: ", arrayBufferToHexString(result)); + console.assert( + arrayBufferToHexString(result) === expected, + 'Empty string hash failed'); + console.log('test_ripemd160_empty_string ok'); +} + +function test_ripemd160_basic_string() { + // RIPEMD160 of "hello" + const input = hexStringToUint8Array('68656c6c6f'); // "hello" in hex + const expected = '108f07b8382412612c048d07d13f814118445acd'; + + const ripemd160 = new hash.Ripemd160(); + const start = ckb.current_cycles(); + ripemd160.write(input.buffer); + const result = ripemd160.finalize(); + const end = ckb.current_cycles(); + console.log(`basic string hash cycles: ${end - start}`); + + console.log("result: ", arrayBufferToHexString(result)); + console.assert( + arrayBufferToHexString(result) === expected, + 'Basic string hash failed'); + console.log('test_ripemd160_basic_string ok'); +} + +function test_ripemd160_multiple_updates() { + // Testing multiple write operations + const input1 = hexStringToUint8Array('68656c'); // "hel" + const input2 = hexStringToUint8Array('6c6f'); // "lo" + const expected = '108f07b8382412612c048d07d13f814118445acd'; + + const ripemd160 = new hash.Ripemd160(); + const start = ckb.current_cycles(); + ripemd160.write(input1.buffer); + ripemd160.write(input2.buffer); + const result = ripemd160.finalize(); + const end = ckb.current_cycles(); + console.log(`multiple updates hash cycles: ${end - start}`); + + console.log("result: ", arrayBufferToHexString(result)); + console.assert( + arrayBufferToHexString(result) === expected, + 'Multiple updates hash failed'); + console.log('test_ripemd160_multiple_updates ok'); +} + +function test_ripemd160_long_string() { + // Testing with a longer string + const input = 'a'.repeat(1000); + const inputBytes = stringToUint8Array(input); + const expected = 'aa69deee9a8922e92f8105e007f76110f381e9cf'; + + const ripemd160 = new hash.Ripemd160(); + const start = ckb.current_cycles(); + ripemd160.write(inputBytes.buffer); + const result = ripemd160.finalize(); + const end = ckb.current_cycles(); + console.log(`long string hash cycles: ${end - start}`); + console.log("result: ", arrayBufferToHexString(result)); + console.assert( + arrayBufferToHexString(result) === expected, 'Long string hash failed'); + console.log('test_ripemd160_long_string ok'); +} + +console.log('test_hash.js ...'); +test_sha2_sha256_empty_string(); +test_sha2_sha256_basic_string(); +test_sha2_sha256_multiple_updates(); +test_sha2_sha256_long_string(); +test_sha2_sha256_error_handling(); +test_keccak256_empty_string(); +test_keccak256_basic_string(); +test_keccak256_multiple_updates(); +test_keccak256_long_string(); +test_blake2b_empty_string(); +test_blake2b_basic_string(); +test_blake2b_multiple_updates(); +test_blake2b_long_string(); +test_ripemd160_empty_string(); +test_ripemd160_basic_string(); +test_ripemd160_multiple_updates(); +test_ripemd160_long_string(); +console.log('test_hash.js ok');