@credithub/jurischain-node 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/binding.gyp ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "targets": [
3
+ {
4
+ "include_dirs": [
5
+ "<!(node -e \"require('nan')\")",
6
+ ".",
7
+ "../../include",
8
+ ],
9
+ "cflags": ["-Wstack-protector", "-Wall", "-g"],
10
+ "target_name": "Jurischain",
11
+ "sources": [ 'main.cpp' ]
12
+ }
13
+ ]
14
+ }
package/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ declare class Jurischain {
2
+ constructor(difficulty: number, seed: string);
3
+ challengeResponse(response: string): boolean;
4
+ readChallenge(): string;
5
+ solveStep(): boolean;
6
+ verify(): boolean;
7
+ }
8
+
9
+ export { Jurischain };
10
+ export default Jurischain;
package/index.js ADDED
@@ -0,0 +1,4 @@
1
+ 'use strict';
2
+
3
+ const { Jurischain } = require('bindings')('Jurischain');
4
+ module.exports = { Jurischain, default: Jurischain };
package/jurischain.h ADDED
@@ -0,0 +1,254 @@
1
+ #ifndef H_JURISCHAIN
2
+ #define H_JURISCHAIN
3
+
4
+ #define JURISCHAIN_VERSION "1.1.3"
5
+
6
+ #include <stdlib.h>
7
+ #include <stdint.h>
8
+ #include <string.h>
9
+
10
+ #ifndef KECCAKF_ROUNDS
11
+ #define KECCAKF_ROUNDS 24
12
+ #endif
13
+
14
+ #ifndef ROTL64
15
+ #define ROTL64(x, y) (((x) << (y)) | ((x) >> (64 - (y))))
16
+ #endif
17
+
18
+ #ifndef HASH_LEN
19
+ #define HASH_LEN 32
20
+ #endif
21
+
22
+ /*
23
+ * Header-only library: every function has internal linkage (static inline)
24
+ * so the header can be #included in multiple translation units without
25
+ * causing duplicate-symbol errors at link time.
26
+ */
27
+
28
+ /* state context */
29
+ typedef struct {
30
+ union { /* state */
31
+ uint8_t b[200]; /* 8-bit bytes */
32
+ uint64_t q[25]; /* 64-bit words */
33
+ } st;
34
+ int pt, rsiz, mdlen; /* these don't overflow */
35
+ } sha3_ctx_t;
36
+
37
+ typedef struct {
38
+ uint8_t payload[HASH_LEN + 1];
39
+ uint8_t seed[HASH_LEN];
40
+ } jurischain_ctx_t;
41
+
42
+ /* Compression function */
43
+ static inline void sha3_keccakf(uint64_t st[25]) {
44
+ /* constants */
45
+ const uint64_t keccakf_rndc[24] = {
46
+ 0x0000000000000001, 0x0000000000008082, 0x800000000000808a,
47
+ 0x8000000080008000, 0x000000000000808b, 0x0000000080000001,
48
+ 0x8000000080008081, 0x8000000000008009, 0x000000000000008a,
49
+ 0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
50
+ 0x000000008000808b, 0x800000000000008b, 0x8000000000008089,
51
+ 0x8000000000008003, 0x8000000000008002, 0x8000000000000080,
52
+ 0x000000000000800a, 0x800000008000000a, 0x8000000080008081,
53
+ 0x8000000000008080, 0x0000000080000001, 0x8000000080008008};
54
+ const int keccakf_rotc[24] = {1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14,
55
+ 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44};
56
+ const int keccakf_piln[24] = {10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4,
57
+ 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1};
58
+
59
+ /* variables */
60
+ int i = 0, j = 0, r = 0;
61
+ uint64_t t = 0, bc[5] = {
62
+ 0,
63
+ };
64
+
65
+ #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
66
+ uint8_t *v;
67
+
68
+ /* endianess conversion. this is redundant on little-endian targets */
69
+ for (i = 0; i < 25; i++) {
70
+ v = (uint8_t *)&st[i];
71
+ st[i] = ((uint64_t)v[0]) | (((uint64_t)v[1]) << 8) |
72
+ (((uint64_t)v[2]) << 16) | (((uint64_t)v[3]) << 24) |
73
+ (((uint64_t)v[4]) << 32) | (((uint64_t)v[5]) << 40) |
74
+ (((uint64_t)v[6]) << 48) | (((uint64_t)v[7]) << 56);
75
+ }
76
+ #endif
77
+
78
+ /* actual iteration */
79
+ for (r = 0; r < KECCAKF_ROUNDS; r++) {
80
+ /* Theta */
81
+ for (i = 0; i < 5; i++)
82
+ bc[i] = st[i] ^ st[i + 5] ^ st[i + 10] ^ st[i + 15] ^ st[i + 20];
83
+
84
+ for (i = 0; i < 5; i++) {
85
+ t = bc[(i + 4) % 5] ^ ROTL64(bc[(i + 1) % 5], 1);
86
+ for (j = 0; j < 25; j += 5) st[j + i] ^= t;
87
+ }
88
+
89
+ /* Rho Pi */
90
+ t = st[1];
91
+ for (i = 0; i < 24; i++) {
92
+ j = keccakf_piln[i];
93
+ bc[0] = st[j];
94
+ st[j] = ROTL64(t, keccakf_rotc[i]);
95
+ t = bc[0];
96
+ }
97
+
98
+ /* Chi */
99
+ for (j = 0; j < 25; j += 5) {
100
+ for (i = 0; i < 5; i++) bc[i] = st[j + i];
101
+ for (i = 0; i < 5; i++) st[j + i] ^= (~bc[(i + 1) % 5]) & bc[(i + 2) % 5];
102
+ }
103
+
104
+ /* Iota */
105
+ st[0] ^= keccakf_rndc[r];
106
+ }
107
+
108
+ #if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
109
+ #error WebAssembly should be little endian
110
+ /* endianess conversion. this is redundant on little-endian targets */
111
+ for (i = 0; i < 25; i++) {
112
+ v = (uint8_t *)&st[i];
113
+ t = st[i];
114
+ v[0] = t & 0xFF;
115
+ v[1] = (t >> 8) & 0xFF;
116
+ v[2] = (t >> 16) & 0xFF;
117
+ v[3] = (t >> 24) & 0xFF;
118
+ v[4] = (t >> 32) & 0xFF;
119
+ v[5] = (t >> 40) & 0xFF;
120
+ v[6] = (t >> 48) & 0xFF;
121
+ v[7] = (t >> 56) & 0xFF;
122
+ }
123
+ #endif
124
+ }
125
+
126
+ /* Initialize the context for SHA3 (mdlen = hash output in bytes) */
127
+ static inline int sha3_init(sha3_ctx_t *c, int mdlen) {
128
+ int i = 0;
129
+
130
+ for (i = 0; i < 25; i++) c->st.q[i] = 0;
131
+ c->mdlen = mdlen;
132
+ c->rsiz = 200 - 2 * mdlen;
133
+ c->pt = 0;
134
+
135
+ return 1;
136
+ }
137
+
138
+ /* update state with more data */
139
+ static inline int sha3_update(sha3_ctx_t *c, const void *data, size_t len) {
140
+ size_t i = 0;
141
+ int j = 0;
142
+
143
+ j = c->pt;
144
+ for (i = 0; i < len; i++) {
145
+ c->st.b[j++] ^= ((const uint8_t *)data)[i];
146
+ if (j >= c->rsiz) {
147
+ sha3_keccakf(c->st.q);
148
+ j = 0;
149
+ }
150
+ }
151
+ c->pt = j;
152
+
153
+ return 1;
154
+ }
155
+
156
+ /* finalize and output a hash (digest goes to md) */
157
+ static inline int sha3_final(void *md, sha3_ctx_t *c) {
158
+ int i = 0;
159
+
160
+ c->st.b[c->pt] ^= 0x06;
161
+ c->st.b[c->rsiz - 1] ^= 0x80;
162
+ sha3_keccakf(c->st.q);
163
+
164
+ for (i = 0; i < c->mdlen; i++) {
165
+ ((uint8_t *)md)[i] = c->st.b[i];
166
+ }
167
+
168
+ return 1;
169
+ }
170
+
171
+ /* compute a SHA-3 hash (md) of given byte length from "in" */
172
+ static inline void *sha3(const void *in, size_t inlen, void *md, int mdlen) {
173
+ sha3_ctx_t sha3;
174
+
175
+ memset(&sha3, 0, sizeof(sha3));
176
+
177
+ sha3_init(&sha3, mdlen);
178
+ sha3_update(&sha3, in, inlen);
179
+ sha3_final(md, &sha3);
180
+
181
+ return md;
182
+ }
183
+
184
+ static inline void jurischain_gen(jurischain_ctx_t *challenge, uint8_t d, const void *seed, size_t inlen) {
185
+ uint8_t rand_hash[HASH_LEN] = { 0, };
186
+ if (!challenge || !seed || inlen == 0) return;
187
+ memset(challenge, 0, sizeof(jurischain_ctx_t));
188
+ sha3(seed, inlen, rand_hash, HASH_LEN);
189
+ memcpy(challenge->seed, rand_hash, sizeof(rand_hash));
190
+ memcpy(challenge->payload, rand_hash, sizeof(rand_hash));
191
+ challenge->payload[HASH_LEN] = d;
192
+ }
193
+
194
+ static inline jurischain_ctx_t *jurischain_init(void) {
195
+ jurischain_ctx_t *ptr = (jurischain_ctx_t *)malloc(sizeof(jurischain_ctx_t));
196
+ if (ptr) memset(ptr, 0, sizeof(jurischain_ctx_t));
197
+ return ptr;
198
+ }
199
+
200
+ static inline void jurischain_destroy(jurischain_ctx_t **ptr) {
201
+ if (!ptr || !*ptr) return;
202
+ memset(*ptr, 0, sizeof(jurischain_ctx_t));
203
+ free(*ptr);
204
+ *ptr = NULL;
205
+ }
206
+
207
+ static inline int jurischain_verify(jurischain_ctx_t *challenge) {
208
+ uint8_t hash_concat[HASH_LEN * 2] = { 0, },
209
+ response[HASH_LEN] = { 0, };
210
+ uint64_t res64[HASH_LEN / 8] = { 0, };
211
+ uint64_t mask = 0, valid = 1, i = 0;
212
+ uint8_t d = 0;
213
+
214
+ if (!challenge) return 0;
215
+
216
+ memcpy(hash_concat, challenge->seed, HASH_LEN);
217
+ memcpy(&hash_concat[HASH_LEN], challenge->payload, HASH_LEN);
218
+
219
+ d = challenge->payload[HASH_LEN];
220
+
221
+ sha3(hash_concat, HASH_LEN * 2, response, HASH_LEN);
222
+
223
+ /*
224
+ * Interpret the digest as little-endian 64-bit words. memcpy avoids the
225
+ * strict-aliasing and unaligned-access undefined behaviour that a raw
226
+ * (uint64_t *) cast over a byte buffer would introduce. WebAssembly and
227
+ * all supported native targets are little-endian.
228
+ */
229
+ memcpy(res64, response, HASH_LEN);
230
+
231
+ /* check that the first `d` bits are zero */
232
+ for (i = 0; i < (d / 64u); i++)
233
+ if (res64[i] != 0) valid = 0;
234
+
235
+ if ((d % 64u) != 0) {
236
+ /* guard: shifting a 64-bit value by 64 is undefined behaviour, so this
237
+ * mask is only ever computed when (d % 64) is in the range 1..63 */
238
+ mask = (uint64_t)0xFFFFFFFFFFFFFFFFULL >> (64u - (d % 64u));
239
+ if ((res64[d / 64u] & mask) != 0) valid = 0;
240
+ }
241
+
242
+ return (int)valid;
243
+ }
244
+
245
+ static inline int jurischain_try(jurischain_ctx_t *challenge) {
246
+ uint8_t rand_hash[HASH_LEN] = { 0, };
247
+ if (!challenge) return 0;
248
+ sha3(challenge->seed, HASH_LEN, rand_hash, HASH_LEN);
249
+ memcpy(challenge->seed, rand_hash, HASH_LEN);
250
+
251
+ return jurischain_verify(challenge);
252
+ }
253
+
254
+ #endif
package/main.cpp ADDED
@@ -0,0 +1,141 @@
1
+ #include <cstdint>
2
+ #include <cstdio>
3
+ #include <cstring>
4
+ #include <nan.h>
5
+
6
+ #include "jurischain.h"
7
+
8
+ class Jurischain : public Nan::ObjectWrap {
9
+ public:
10
+ static NAN_MODULE_INIT(Init) {
11
+ v8::Local<v8::FunctionTemplate> ctor =
12
+ Nan::New<v8::FunctionTemplate>(Jurischain::New);
13
+ static Nan::Persistent<v8::Function> constructor;
14
+
15
+ ctor->SetClassName(Nan::New("Jurischain").ToLocalChecked());
16
+ ctor->InstanceTemplate()->SetInternalFieldCount(1);
17
+
18
+ Nan::SetPrototypeMethod(ctor, "challengeResponse", Define);
19
+ Nan::SetPrototypeMethod(ctor, "readChallenge", Retrieve);
20
+ Nan::SetPrototypeMethod(ctor, "solveStep", Solve);
21
+ Nan::SetPrototypeMethod(ctor, "verify", Verify);
22
+
23
+ constructor.Reset(Nan::GetFunction(ctor).ToLocalChecked());
24
+ Nan::Set(target, Nan::New("Jurischain").ToLocalChecked(),
25
+ Nan::GetFunction(ctor).ToLocalChecked());
26
+ }
27
+
28
+ private:
29
+ jurischain_ctx_t context;
30
+
31
+ explicit Jurischain(uint8_t d, const void *seed, size_t inlen) {
32
+ std::memset(&context, 0, sizeof(context));
33
+ jurischain_gen(&context, d, seed, inlen);
34
+ }
35
+
36
+ ~Jurischain() {
37
+ std::memset(&context, 0, sizeof(context));
38
+ }
39
+
40
+ static NAN_METHOD(New) {
41
+ if (!info.IsConstructCall()) {
42
+ return Nan::ThrowError("Jurischain must be called with the new keyword");
43
+ }
44
+
45
+ if (info.Length() < 2) {
46
+ return Nan::ThrowTypeError("expected 2 arguments: difficulty (number) and seed (string)");
47
+ }
48
+
49
+ if (!info[0]->IsNumber()) {
50
+ return Nan::ThrowTypeError("difficulty must be a number");
51
+ }
52
+
53
+ double rawDifficulty = Nan::To<double>(info[0]).FromMaybe(0.0);
54
+
55
+ if (rawDifficulty != static_cast<int>(rawDifficulty)) {
56
+ return Nan::ThrowTypeError("difficulty must be an integer");
57
+ }
58
+
59
+ if (rawDifficulty < 1 || rawDifficulty > 255) {
60
+ return Nan::ThrowRangeError("difficulty must be between 1 and 255");
61
+ }
62
+
63
+ if (!info[1]->IsString()) {
64
+ return Nan::ThrowTypeError("seed must be a string");
65
+ }
66
+
67
+ Nan::Utf8String seed(info[1]);
68
+
69
+ if (seed.length() <= 0 || *seed == nullptr) {
70
+ return Nan::ThrowTypeError("seed must be a non-empty string");
71
+ }
72
+
73
+ uint8_t difficulty = static_cast<uint8_t>(rawDifficulty);
74
+ Jurischain *obj = new Jurischain(difficulty,
75
+ static_cast<const void *>(*seed), seed.length());
76
+ obj->Wrap(info.This());
77
+ info.GetReturnValue().Set(info.This());
78
+ }
79
+
80
+ static NAN_METHOD(Verify) {
81
+ Jurischain *obj = Nan::ObjectWrap::Unwrap<Jurischain>(info.This());
82
+ info.GetReturnValue().Set(
83
+ Nan::New<v8::Boolean>(jurischain_verify(&obj->context) != 0));
84
+ }
85
+
86
+ static NAN_METHOD(Solve) {
87
+ Jurischain *obj = Nan::ObjectWrap::Unwrap<Jurischain>(info.This());
88
+ info.GetReturnValue().Set(
89
+ Nan::New<v8::Boolean>(jurischain_try(&obj->context) != 0));
90
+ }
91
+
92
+ static NAN_METHOD(Define) {
93
+ if (info.Length() < 1) {
94
+ return Nan::ThrowTypeError("expected 1 argument: hex response string");
95
+ }
96
+
97
+ if (!info[0]->IsString()) {
98
+ return Nan::ThrowTypeError("response must be a string");
99
+ }
100
+
101
+ Nan::Utf8String response(info[0]);
102
+ const int expectedLen = HASH_LEN * 2;
103
+
104
+ if (response.length() != expectedLen) {
105
+ return Nan::ThrowRangeError(
106
+ "response must be exactly 64 hex characters");
107
+ }
108
+
109
+ const char *hex = *response;
110
+ if (hex == nullptr) {
111
+ return Nan::ThrowTypeError("response string is invalid");
112
+ }
113
+
114
+ Jurischain *obj = Nan::ObjectWrap::Unwrap<Jurischain>(info.This());
115
+
116
+ for (size_t i = 0; i < HASH_LEN; i++) {
117
+ if (std::sscanf(hex + (i * 2), "%02hhX", &obj->context.seed[i]) != 1) {
118
+ std::memset(obj->context.seed, 0, HASH_LEN);
119
+ char errmsg[64];
120
+ std::snprintf(errmsg, sizeof(errmsg),
121
+ "response contains invalid hex at position %zu", i * 2);
122
+ return Nan::ThrowError(errmsg);
123
+ }
124
+ }
125
+
126
+ info.GetReturnValue().Set(Nan::New<v8::Boolean>(true));
127
+ }
128
+
129
+ static NAN_METHOD(Retrieve) {
130
+ Jurischain *obj = Nan::ObjectWrap::Unwrap<Jurischain>(info.This());
131
+
132
+ char str[(HASH_LEN * 2) + 1] = { 0 };
133
+ for (int i = 0; i < HASH_LEN; i++) {
134
+ std::snprintf(str + (i * 2), 3, "%02hhX", obj->context.seed[i]);
135
+ }
136
+
137
+ info.GetReturnValue().Set(Nan::New(str).ToLocalChecked());
138
+ }
139
+ };
140
+
141
+ NODE_MODULE(Jurischain, Jurischain::Init);
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@credithub/jurischain-node",
3
+ "version": "1.0.2",
4
+ "description": "Native Node.js binding for JurisChain Proof-of-Work CAPTCHA. Challenges terminals instead of humans to mitigate DDoS attacks.",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "types": "./index.d.ts",
10
+ "require": "./index.js"
11
+ }
12
+ },
13
+ "files": [
14
+ "index.js",
15
+ "index.d.ts",
16
+ "main.cpp",
17
+ "jurischain.h",
18
+ "binding.gyp"
19
+ ],
20
+ "scripts": {
21
+ "prepack": "node -e \"require('fs').copyFileSync('../../include/jurischain.h','jurischain.h')\"",
22
+ "install": "node-gyp rebuild",
23
+ "rebuild": "node-gyp rebuild",
24
+ "test": "node -e \"const { Jurischain } = require('.'); const j = new Jurischain(1, 'test'); console.log('OK:', j.readChallenge());\""
25
+ },
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/credithub/jurischain.git"
29
+ },
30
+ "keywords": [
31
+ "captcha",
32
+ "proof-of-work",
33
+ "sha3",
34
+ "blockchain"
35
+ ],
36
+ "author": "CreditHub <tech@credithub.com.br>",
37
+ "license": "MIT",
38
+ "bugs": {
39
+ "url": "https://github.com/credithub/jurischain/issues"
40
+ },
41
+ "engines": {
42
+ "node": ">=14.0.0"
43
+ },
44
+ "dependencies": {
45
+ "bindings": "^1.5.0",
46
+ "nan": "^2.19.0"
47
+ },
48
+ "devDependencies": {
49
+ "node-gyp": "^10.0.0"
50
+ },
51
+ "homepage": "https://github.com/credithub/jurischain#readme"
52
+ }