diff --git a/README.md b/README.md index d593a28e..430871ae 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,12 @@ Algorithms Usage ----- -Install +Install on Ubuntu 16.04 ```bash -npm install multi-hashing +sudo apt-get nodejs nodejs-dev node-gyp npm +sudo ln -s /usr/bin/nodejs /usr/bin/node +npm install git+https://github.com/sumlnoether/node-multi-hashing-node8.git ``` So far this native Node.js addon can do the following hashing algos @@ -79,3 +81,5 @@ Credits * [bcrypt](http://en.wikipedia.org/wiki/Bcrypt) - Niels Provos and David Mazières * [X11](http://www.darkcoin.io/), [Hefty1](http://heavycoin.github.io/about.html), [Quark](http://www.qrk.cc/) creators (they just mixed together a bunch of the above algos) * [PhearZero](https://github.com/PhearZero) Michael J Feher +* [codebling](https://github.com/codebling) CodeBling +* [Monero](https://github.com/monero-project/monero) The Monero Project diff --git a/cryptonight.c b/cryptonight.c index a2642268..2d0ca800 100644 --- a/cryptonight.c +++ b/cryptonight.c @@ -1,7 +1,11 @@ // Copyright (c) 2012-2013 The Cryptonote developers // Distributed under the MIT/X11 software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +// Portions Copyright (c) 2018 The Monero developers +#include +#include +#include #include "crypto/oaes_lib.h" #include "crypto/c_keccak.h" #include "crypto/c_groestl.h" @@ -17,11 +21,40 @@ #define MEMORY (1 << 21) /* 2 MiB */ #define ITER (1 << 20) +#define MASK 0x1FFFF + +#define LITE_MEMORY (1 << 20) /* 2 MiB */ +#define LITE_ITER (1 << 19) +#define LITE_MASK 0xFFFF + #define AES_BLOCK_SIZE 16 #define AES_KEY_SIZE 32 /*16*/ #define INIT_SIZE_BLK 8 #define INIT_SIZE_BYTE (INIT_SIZE_BLK * AES_BLOCK_SIZE) +#define VARIANT1_1(p) \ + do if (variant > 0) \ + { \ + const uint8_t tmp = ((const uint8_t*)(p))[11]; \ + static const uint32_t table = 0x75310; \ + const uint8_t index = (((tmp >> 3) & 6) | (tmp & 1)) << 1; \ + ((uint8_t*)(p))[11] = tmp ^ ((table >> index) & 0x30); \ + } while(0) + +#define VARIANT1_2(p) \ + do if (variant > 0) \ + { \ + ((uint64_t*)p)[1] ^= tweak1_2; \ + } while(0) + +#define VARIANT1_INIT() \ + if (variant > 0 && len < 43) \ + { \ + fprintf(stderr, "Cryptonight variants need at least 43 bytes of data"); \ + _exit(1); \ + } \ + const uint64_t tweak1_2 = variant > 0 ? *(const uint64_t*)(((const uint8_t*)input)+35) ^ ctx->state.hs.w[24] : 0 + #pragma pack(push, 1) union cn_slow_hash_state { union hash_state hs; @@ -57,8 +90,8 @@ static void (* const extra_hashes[4])(const void *, size_t, char *) = { extern int aesb_single_round(const uint8_t *in, uint8_t*out, const uint8_t *expandedKey); extern int aesb_pseudo_round(const uint8_t *in, uint8_t *out, const uint8_t *expandedKey); -static inline size_t e2i(const uint8_t* a) { - return (*((uint64_t*) a) / AES_BLOCK_SIZE) & (MEMORY / AES_BLOCK_SIZE - 1); +static inline size_t e2i(const uint8_t* a, size_t mask) { + return (*((uint64_t*) a) / AES_BLOCK_SIZE) & mask; } static void mul(const uint8_t* a, const uint8_t* b, uint8_t* res) { @@ -124,16 +157,21 @@ struct cryptonight_ctx { oaes_ctx* aes_ctx; }; -void cryptonight_hash(const char* input, char* output, uint32_t len) { +void cryptonight_hash(const char* input, char* output, uint32_t len, int variant, int lite) { struct cryptonight_ctx *ctx = alloca(sizeof(struct cryptonight_ctx)); hash_process(&ctx->state.hs, (const uint8_t*) input, len); memcpy(ctx->text, ctx->state.init, INIT_SIZE_BYTE); memcpy(ctx->aes_key, ctx->state.hs.b, AES_KEY_SIZE); ctx->aes_ctx = (oaes_ctx*) oaes_alloc(); size_t i, j; + size_t memory = lite ? LITE_MEMORY : MEMORY; + size_t iterations = lite ? LITE_ITER : ITER; + size_t mask = lite ? LITE_MASK : MASK; + + VARIANT1_INIT(); oaes_key_import_data(ctx->aes_ctx, ctx->aes_key, AES_KEY_SIZE); - for (i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) { + for (i = 0; i < memory / INIT_SIZE_BYTE; i++) { for (j = 0; j < INIT_SIZE_BLK; j++) { aesb_pseudo_round(&ctx->text[AES_BLOCK_SIZE * j], &ctx->text[AES_BLOCK_SIZE * j], @@ -147,24 +185,27 @@ void cryptonight_hash(const char* input, char* output, uint32_t len) { ctx->b[i] = ctx->state.k[16 + i] ^ ctx->state.k[48 + i]; } - for (i = 0; i < ITER / 2; i++) { + for (i = 0; i < iterations / 2; i++) { /* Dependency chain: address -> read value ------+ * written value <-+ hard function (AES or MUL) <+ * next address <-+ */ /* Iteration 1 */ - j = e2i(ctx->a); + j = e2i(ctx->a, mask); aesb_single_round(&ctx->long_state[j * AES_BLOCK_SIZE], ctx->c, ctx->a); xor_blocks_dst(ctx->c, ctx->b, &ctx->long_state[j * AES_BLOCK_SIZE]); + VARIANT1_1((uint8_t*)&ctx->long_state[j * AES_BLOCK_SIZE]); /* Iteration 2 */ mul_sum_xor_dst(ctx->c, ctx->a, - &ctx->long_state[e2i(ctx->c) * AES_BLOCK_SIZE]); + &ctx->long_state[e2i(ctx->c, mask) * AES_BLOCK_SIZE]); copy_block(ctx->b, ctx->c); + VARIANT1_2((uint8_t*) + &ctx->long_state[e2i(ctx->c, mask) * AES_BLOCK_SIZE]); } memcpy(ctx->text, ctx->state.init, INIT_SIZE_BYTE); oaes_key_import_data(ctx->aes_ctx, &ctx->state.hs.b[32], AES_KEY_SIZE); - for (i = 0; i < MEMORY / INIT_SIZE_BYTE; i++) { + for (i = 0; i < memory / INIT_SIZE_BYTE; i++) { for (j = 0; j < INIT_SIZE_BLK; j++) { xor_blocks(&ctx->text[j * AES_BLOCK_SIZE], &ctx->long_state[i * INIT_SIZE_BYTE + j * AES_BLOCK_SIZE]); diff --git a/cryptonight.h b/cryptonight.h index 86fedca6..d4165e38 100644 --- a/cryptonight.h +++ b/cryptonight.h @@ -7,7 +7,7 @@ extern "C" { #include -void cryptonight_hash(const char* input, char* output, uint32_t len); +void cryptonight_hash(const char* input, char* output, uint32_t len, int variant, int lite); void cryptonight_fast_hash(const char* input, char* output, uint32_t len); #ifdef __cplusplus diff --git a/multihashing.cpp b/multihashing.cpp index 74f245a4..9001c2ae 100644 --- a/multihashing.cpp +++ b/multihashing.cpp @@ -21,7 +21,7 @@ extern "C" { #include "cryptonight.h" #include "x13.h" #include "nist5.h" - #include "sha1.h", + #include "sha1.h" #include "x15.h" #include "fresh.h" } @@ -49,7 +49,7 @@ NAN_METHOD(quark) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); quark_hash(input, output, input_len); @@ -86,18 +86,18 @@ NAN_METHOD(scrypt) { if(!node::Buffer::HasInstance(target)) return except("Argument should be a buffer object."); - + Local numn = info[1]->ToNumber(); unsigned int nValue = numn->Value(); Local numr = info[2]->ToNumber(); unsigned int rValue = numr->Value(); - + char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); uint32_t input_len = node::Buffer::Length(target); - + scrypt_N_R_1_256(input, output, nValue, rValue, input_len); info.GetReturnValue().Set(dest.ToLocalChecked()); @@ -216,7 +216,7 @@ NAN_METHOD(skein) { char* output = node::Buffer::Data(dest.ToLocalChecked()); uint32_t input_len = node::Buffer::Length(target); - + skein_hash(input, output, input_len); info.GetReturnValue().Set(dest.ToLocalChecked()); @@ -235,7 +235,7 @@ NAN_METHOD(groestl) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); groestl_hash(input, output, input_len); @@ -256,7 +256,7 @@ NAN_METHOD(groestlmyriad) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); groestlmyriad_hash(input, output, input_len); @@ -277,7 +277,7 @@ NAN_METHOD(blake) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); blake_hash(input, output, input_len); @@ -298,7 +298,7 @@ NAN_METHOD(fugue) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); fugue_hash(input, output, input_len); @@ -319,7 +319,7 @@ NAN_METHOD(qubit) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); qubit_hash(input, output, input_len); @@ -340,7 +340,7 @@ NAN_METHOD(hefty1) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); hefty1_hash(input, output, input_len); @@ -361,7 +361,7 @@ NAN_METHOD(shavite3) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); shavite3_hash(input, output, input_len); @@ -371,14 +371,60 @@ NAN_METHOD(shavite3) { NAN_METHOD(cryptonight) { bool fast = false; + uint32_t cn_variant = 0; + + if (info.Length() < 1) + return except("You must provide one argument."); + + if (info.Length() >= 2) { + if (info.Length() >= 2) { + if(info[1]->IsBoolean()) + fast = info[1]->ToBoolean()->BooleanValue(); + else if(info[1]->IsUint32()) + cn_variant = info[1]->ToUint32()->Uint32Value(); + else + return except("Argument 2 should be a boolean or uint32_t"); + } + } + + Local target = info[0]->ToObject(); + + if(!node::Buffer::HasInstance(target)) + return except("Argument should be a buffer object."); + + char * input = node::Buffer::Data(target); + Nan::MaybeLocal dest = Nan::NewBuffer(32); + char* output = node::Buffer::Data(dest.ToLocalChecked()); + + uint32_t input_len = node::Buffer::Length(target); + + if(fast) + cryptonight_fast_hash(input, output, input_len); + else { + if (cn_variant > 0 && input_len < 43) + return except("Argument must be 43 bytes for monero variant 1+"); + cryptonight_hash(input, output, input_len, cn_variant, 0); + } + + info.GetReturnValue().Set(dest.ToLocalChecked()); +} + +NAN_METHOD(cryptonightlite) { + bool fast = false; + uint32_t cn_variant = 0; if (info.Length() < 1) return except("You must provide one argument."); - + if (info.Length() >= 2) { - if(!info[1]->IsBoolean()) - return except("Argument 2 should be a boolean"); - fast = info[1]->ToBoolean()->BooleanValue(); + if (info.Length() >= 2) { + if(info[1]->IsBoolean()) + fast = info[1]->ToBoolean()->BooleanValue(); + else if(info[1]->IsUint32()) + cn_variant = info[1]->ToUint32()->Uint32Value(); + else + return except("Argument 2 should be a boolean or uint32_t"); + } } Local target = info[0]->ToObject(); @@ -389,13 +435,16 @@ NAN_METHOD(cryptonight) { char * input = node::Buffer::Data(target); Nan::MaybeLocal dest = Nan::NewBuffer(32); char* output = node::Buffer::Data(dest.ToLocalChecked()); - + uint32_t input_len = node::Buffer::Length(target); if(fast) cryptonight_fast_hash(input, output, input_len); - else - cryptonight_hash(input, output, input_len); + else { + if (cn_variant > 0 && input_len < 43) + return except("Argument must be 43 bytes for aeon variant 1+"); + cryptonight_hash(input, output, input_len, cn_variant, 1); + } info.GetReturnValue().Set(dest.ToLocalChecked()); } @@ -566,6 +615,8 @@ NAN_MODULE_INIT(Init) { GetFunction(New(shavite3)).ToLocalChecked()); Nan::Set(target, New("cryptonight").ToLocalChecked(), GetFunction(New(cryptonight)).ToLocalChecked()); + Nan::Set(target, New("cryptonight-lite").ToLocalChecked(), + GetFunction(New(cryptonightlite)).ToLocalChecked()); Nan::Set(target, New("x13").ToLocalChecked(), GetFunction(New(x13)).ToLocalChecked()); Nan::Set(target, New("boolberry").ToLocalChecked(), @@ -581,4 +632,4 @@ NAN_MODULE_INIT(Init) { } NODE_MODULE(multihashing, Init) -} \ No newline at end of file +} diff --git a/tests/cryptonight-tests.js b/tests/cryptonight-tests.js new file mode 100644 index 00000000..41ecb3ad --- /dev/null +++ b/tests/cryptonight-tests.js @@ -0,0 +1,24 @@ +const multiHashing = require('../build/Release/multihashing'); +const assert = require('assert'); + +var cn_data = new Buffer("6465206f6d6e69627573206475626974616e64756d", "hex"); +var xmrig_data = new Buffer("0100fb8e8ac805899323371bb790db19218afd8db8e3755d8b90f39b3d5506a9abce4fa912244500000000ee8146d49fa93ee724deb57d12cbc6c6f3b924d946127c7a97418f9348828f0f02", "hex"); +var cn_hash = new Buffer("2f8e3df40bd11f9ac90c743ca8e32bb391da4fb98612aa3b6cdc639ee00b31f5", "hex"); +var xmrig_cnvariant1_hash = new Buffer("c9fae8425d8688dc236bcdbc42fdb42d376c6ec190501aa84b04a4b4cf1ee122", "hex"); +var xmrig_cnlite_hash = new Buffer("28a22bad3f93d1408fca472eb5ad1cbe75f21d053c8ce5b3af105a57713e21dd", "hex"); +var xmrig_cnlitevariant1_hash = new Buffer("87c4e570653eb4c2b42b7a0d546559452dfab573b82ec52f152b7ff98e79446f", "hex"); + +hashedData = multiHashing['cryptonight'](cn_data); +cn_variant1Data = multiHashing['cryptonight'](xmrig_data, 1); +cnlite_data = multiHashing['cryptonight-lite'](xmrig_data, 0); +cnlite_variant1Data = multiHashing['cryptonight-lite'](xmrig_data, 1); + +console.log(hashedData); +console.log(cn_variant1Data); +console.log(cnlite_data); +console.log(cnlite_variant1Data); + +assert.deepEqual(hashedData, cn_hash); +assert.deepEqual(cn_variant1Data, xmrig_cnvariant1_hash); +assert.deepEqual(cnlite_data, xmrig_cnlite_hash); +assert.deepEqual(cnlite_variant1Data, xmrig_cnlitevariant1_hash); diff --git a/tests/test.js b/tests/test.js new file mode 100644 index 00000000..6ffa56d9 --- /dev/null +++ b/tests/test.js @@ -0,0 +1,17 @@ +var multiHashing = require('../build/Release/multihashing'); + +var algorithms = ['keccak', 'groestl', 'skein', 'blake', 'cryptonight', 'cryptonight v7']; + +var data = new Buffer("7000000001e980924e4e1109230383e66d62945ff8e749903bea4336755c00000000000051928aff1b4d72416173a8c3948159a09a73ac3bb556aa6bfbcad1a85da7f4c1d13350531e24031b939b9e2b", "hex"); + +var hashedData = algorithms.map(function(algo){ + if (algo === 'cryptonight v7'){ + return multiHashing['cryptonight'](data, 1); + } + else{ + return multiHashing[algo](data); + } +}); + + +console.log(hashedData);