diff --git a/README.md b/README.md index 0984b8ee..9263fca7 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,7 @@ Currently includes: This miner is multicore and multithreads, one thread is used to mine and other is implementing stratum work and wifi stuff. Every time an stratum job notification is received miner update its current work to not create stale shares. -**IMPORTANT** Miner is not seen by pools due to its low share difficulty. You can check miner work seeing logs via UART or mainly setting up your own pool with a low difficulty rate as 1e-9 +**IMPORTANT** Miner is not seen by all standard pools due to its low share difficulty. You can check miner work remotely using specific pools specified down or seeing logs via UART. ***Current project is still in developement and more features will be added*** @@ -71,9 +71,17 @@ After programming, you will only need to setup your Wifi and BTC address. 1. Setup your Wifi Network 1. Add your BTCaddress -Optional you can select other pool: +Recommended low difficulty share pools: -| Pool URL | Port | URL | +| Pool URL | Port | Web URL | Status | +|--- |--- |--- |--- | +| public-pool.airdns.org | 21496 | https://public-pool.airdns.org:37273/ | Open Source Solo Bitcoin Mining Pool supporting open source miners | +| nerdminers.org | | https://nerdminers.org | Team domain for future pool - Currently pointing to public-pool.airdns.org | +| nerdminer.io | 3333 | https://nerdminer.io | Mantained by CHMEX | + +Other standard pools not compatible with low difficulty share: + +| Pool URL | Port | Web URL | |--- |--- |--- | | solo.ckpool.org | 3333 | https://solo.ckpool.org/ | | btc.zsolo.bid | 6057 | https://zsolo.bid/en/btc-solo-mining-pool | diff --git a/bin/0x10000_firmware_v1.6.0.bin b/bin/0x10000_firmware_v1.6.0.bin new file mode 100644 index 00000000..9f1d23ce Binary files /dev/null and b/bin/0x10000_firmware_v1.6.0.bin differ diff --git a/src/NerdMinerV2.ino.cpp b/src/NerdMinerV2.ino.cpp index 82d34253..8c148474 100644 --- a/src/NerdMinerV2.ino.cpp +++ b/src/NerdMinerV2.ino.cpp @@ -16,10 +16,12 @@ #include "mining.h" #include "monitor.h" -#define CURRENT_VERSION "V1.5.2" +#define CURRENT_VERSION "V1.6.0" //3 seconds WDT #define WDT_TIMEOUT 3 +//120 seconds WDT for miner task +#define WDT_MINER_TIMEOUT 120 OneButton button1(PIN_BUTTON_1); OneButton button2(PIN_BUTTON_2); @@ -56,6 +58,7 @@ void setup() Serial.setTimeout(0); delay(100); + esp_task_wdt_init(WDT_MINER_TIMEOUT, true); // Idle task that would reset WDT never runs, because core 0 gets fully utilized disableCore0WDT(); //disableCore1WDT(); @@ -137,8 +140,11 @@ void setup() // Start mining tasks //BaseType_t res = xTaskCreate(runWorker, name, 35000, (void*)name, 1, NULL); - xTaskCreate(runMiner, "Miner0", 15000, NULL, 1, NULL); - xTaskCreate(runMiner, "Miner1", 15000, NULL, 1, NULL); + TaskHandle_t minerTask1, minerTask2 = NULL; + xTaskCreate(runMiner, "Miner0", 15000, (void*)0, 1, &minerTask1); + xTaskCreate(runMiner, "Miner1", 15000, (void*)1, 1, &minerTask2); + esp_task_wdt_add(minerTask1); + esp_task_wdt_add(minerTask2); /******** MONITOR SETUP *****/ setup_monitor(); @@ -164,4 +170,4 @@ void loop() { wifiManagerProcess(); // avoid delays() in loop when non-blocking and other long running code vTaskDelay(50 / portTICK_PERIOD_MS); -} \ No newline at end of file +} diff --git a/src/ShaTests/customSHA256.cpp b/src/ShaTests/customSHA256.cpp deleted file mode 100644 index b1239454..00000000 --- a/src/ShaTests/customSHA256.cpp +++ /dev/null @@ -1,222 +0,0 @@ -#include "customSHA256.h" - -#define TOTAL_LEN_LEN 8 - -/* - * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here. - * When useful for clarification, portions of the pseudo-code are reproduced here too. - */ - -/* - * @brief Rotate a 32-bit value by a number of bits to the right. - * @param value The value to be rotated. - * @param count The number of bits to rotate by. - * @return The rotated value. - */ -static inline uint32_t right_rot(uint32_t value, unsigned int count) -{ - /* - * Defined behaviour in standard C for all count where 0 < count < 32, which is what we need here. - */ - return value >> count | value << (32 - count); -} - -/* - * @brief Update a hash value under calculation with a new chunk of data. - * @param h Pointer to the first hash item, of a total of eight. - * @param p Pointer to the chunk data, which has a standard length. - * - * @note This is the SHA-256 work horse. - */ -static inline void consume_chunk(uint32_t *h, const uint8_t *p) -{ - unsigned i, j; - uint32_t ah[8]; - - /* Initialize working variables to current hash value: */ - for (i = 0; i < 8; i++) - ah[i] = h[i]; - - /* - * The w-array is really w[64], but since we only need 16 of them at a time, we save stack by - * calculating 16 at a time. - * - * This optimization was not there initially and the rest of the comments about w[64] are kept in their - * initial state. - */ - - /* - * create a 64-entry message schedule array w[0..63] of 32-bit words (The initial values in w[0..63] - * don't matter, so many implementations zero them here) copy chunk into first 16 words w[0..15] of the - * message schedule array - */ - uint32_t w[16]; - - /* Compression function main loop: */ - for (i = 0; i < 4; i++) { - for (j = 0; j < 16; j++) { - if (i == 0) { - w[j] = - (uint32_t)p[0] << 24 | (uint32_t)p[1] << 16 | (uint32_t)p[2] << 8 | (uint32_t)p[3]; - p += 4; - } else { - /* Extend the first 16 words into the remaining 48 words w[16..63] of the - * message schedule array: */ - const uint32_t s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ - (w[(j + 1) & 0xf] >> 3); - const uint32_t s1 = right_rot(w[(j + 14) & 0xf], 17) ^ - right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10); - w[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1; - } - const uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25); - const uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]); - - /* - * Initialize array of round constants: - * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311): - */ - static const uint32_t k[] = { - 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}; - - const uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j]; - const uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22); - const uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]); - const uint32_t temp2 = s0 + maj; - - ah[7] = ah[6]; - ah[6] = ah[5]; - ah[5] = ah[4]; - ah[4] = ah[3] + temp1; - ah[3] = ah[2]; - ah[2] = ah[1]; - ah[1] = ah[0]; - ah[0] = temp1 + temp2; - } - } - - /* Add the compressed chunk to the current hash value: */ - for (i = 0; i < 8; i++) - h[i] += ah[i]; -} - -/* - * Public functions. See header file for documentation. - */ - -void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]) -{ - sha_256->hash = hash; - sha_256->chunk_pos = sha_256->chunk; - sha_256->space_left = SIZE_OF_SHA_256_CHUNK; - sha_256->total_len = 0; - /* - * Initialize hash values (first 32 bits of the fractional parts of the square roots of the first 8 primes - * 2..19): - */ - sha_256->h[0] = 0x6a09e667; - sha_256->h[1] = 0xbb67ae85; - sha_256->h[2] = 0x3c6ef372; - sha_256->h[3] = 0xa54ff53a; - sha_256->h[4] = 0x510e527f; - sha_256->h[5] = 0x9b05688c; - sha_256->h[6] = 0x1f83d9ab; - sha_256->h[7] = 0x5be0cd19; -} - -void sha_256_write(struct Sha_256 *sha_256, const uint8_t *data, size_t len) -{ - sha_256->total_len += len; - - const uint8_t *p = data; - - while (len > 0) { - /* - * If the input chunks have sizes that are multiples of the calculation chunk size, no copies are - * necessary. We operate directly on the input data instead. - */ - if (sha_256->space_left == SIZE_OF_SHA_256_CHUNK && len >= SIZE_OF_SHA_256_CHUNK) { - consume_chunk(sha_256->h, p); - len -= SIZE_OF_SHA_256_CHUNK; - p += SIZE_OF_SHA_256_CHUNK; - continue; - } - /* General case, no particular optimization. */ - const size_t consumed_len = len < sha_256->space_left ? len : sha_256->space_left; - memcpy(sha_256->chunk_pos, p, consumed_len); - sha_256->space_left -= consumed_len; - len -= consumed_len; - p += consumed_len; - if (sha_256->space_left == 0) { - consume_chunk(sha_256->h, sha_256->chunk); - sha_256->chunk_pos = sha_256->chunk; - sha_256->space_left = SIZE_OF_SHA_256_CHUNK; - } else { - sha_256->chunk_pos += consumed_len; - } - } -} - -uint8_t *sha_256_close(struct Sha_256 *sha_256) -{ - uint8_t *pos = sha_256->chunk_pos; - size_t space_left = sha_256->space_left; - uint32_t *const h = sha_256->h; - - /* - * The current chunk cannot be full. Otherwise, it would already have been consumed. I.e. there is space left for - * at least one byte. The next step in the calculation is to add a single one-bit to the data. - */ - *pos++ = 0x80; - --space_left; - - /* - * Now, the last step is to add the total data length at the end of the last chunk, and zero padding before - * that. But we do not necessarily have enough space left. If not, we pad the current chunk with zeroes, and add - * an extra chunk at the end. - */ - if (space_left < TOTAL_LEN_LEN) { - memset(pos, 0x00, space_left); - consume_chunk(h, sha_256->chunk); - pos = sha_256->chunk; - space_left = SIZE_OF_SHA_256_CHUNK; - } - const size_t left = space_left - TOTAL_LEN_LEN; - memset(pos, 0x00, left); - pos += left; - size_t len = sha_256->total_len; - pos[7] = (uint8_t)(len << 3); - len >>= 5; - int i; - for (i = 6; i >= 0; --i) { - pos[i] = (uint8_t)len; - len >>= 8; - } - consume_chunk(h, sha_256->chunk); - /* Produce the final hash value (big-endian): */ - int j; - uint8_t *const hash = sha_256->hash; - for (i = 0, j = 0; i < 8; i++) { - hash[j++] = (uint8_t)(h[i] >> 24); - hash[j++] = (uint8_t)(h[i] >> 16); - hash[j++] = (uint8_t)(h[i] >> 8); - hash[j++] = (uint8_t)h[i]; - } - return sha_256->hash; -} - -void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const uint8_t *input, size_t len) -{ - struct Sha_256 sha_256; - sha_256_init(&sha_256, hash); - sha_256_write(&sha_256, input, len); - (void)sha_256_close(&sha_256); -} \ No newline at end of file diff --git a/src/ShaTests/customSHA256.h b/src/ShaTests/customSHA256.h deleted file mode 100644 index 961a41e6..00000000 --- a/src/ShaTests/customSHA256.h +++ /dev/null @@ -1,103 +0,0 @@ -#ifndef SHA_256_H -#define SHA_256_H - -#include -#include - -#ifdef __cplusplus -extern "C" { -#endif - -/* - * @brief Size of the SHA-256 sum. This times eight is 256 bits. - */ -#define SIZE_OF_SHA_256_HASH 32 - -/* - * @brief Size of the chunks used for the calculations. - * - * @note This should mostly be ignored by the user, although when using the streaming API, it has an impact for - * performance. Add chunks whose size is a multiple of this, and you will avoid a lot of superfluous copying in RAM! - */ -#define SIZE_OF_SHA_256_CHUNK 64 - -/* - * @brief The opaque SHA-256 type, that should be instantiated when using the streaming API. - * - * @note Although the details are exposed here, in order to make instantiation easy, you should refrain from directly - * accessing the fields, as they may change in the future. - */ -struct Sha_256 { - uint8_t *hash; - uint8_t chunk[SIZE_OF_SHA_256_CHUNK]; - uint8_t *chunk_pos; - size_t space_left; - size_t total_len; - uint32_t h[8]; -}; - -/* - * @brief The simple SHA-256 calculation function. - * @param hash Hash array, where the result is delivered. - * @param input Pointer to the data the hash shall be calculated on. - * @param len Length of the input data, in byte. - * - * @note If all of the data you are calculating the hash value on is available in a contiguous buffer in memory, this is - * the function you should use. - * - * @note If either of the passed pointers is NULL, the results are unpredictable. - */ -void calc_sha_256(uint8_t hash[SIZE_OF_SHA_256_HASH], const uint8_t *input, size_t len); - -/* - * @brief Initialize a SHA-256 streaming calculation. - * @param sha_256 A pointer to a SHA-256 structure. - * @param hash Hash array, where the result will be delivered. - * - * @note If all of the data you are calculating the hash value on is not available in a contiguous buffer in memory, this is - * where you should start. Instantiate a SHA-256 structure, for instance by simply declaring it locally, make your hash - * buffer available, and invoke this function. Once a SHA-256 hash has been calculated (see further below) a SHA-256 - * structure can be initialized again for the next calculation. - * - * @note If either of the passed pointers is NULL, the results are unpredictable. - */ -void sha_256_init(struct Sha_256 *sha_256, uint8_t hash[SIZE_OF_SHA_256_HASH]); - -/* - * @brief Stream more input data for an on-going SHA-256 calculation. - * @param sha_256 A pointer to a previously initialized SHA-256 structure. - * @param data Pointer to the data to be added to the calculation. - * @param len Length of the data to add, in byte. - * - * @note This function may be invoked an arbitrary number of times between initialization and closing, but the maximum - * data length is limited by the SHA-256 algorithm: the total number of bits (i.e. the total number of bytes times - * eight) must be representable by a 64-bit unsigned integer. While that is not a practical limitation, the results are - * unpredictable if that limit is exceeded. - * - * @note This function may be invoked on empty data (zero length), although that obviously will not add any data. - * - * @note If either of the passed pointers is NULL, the results are unpredictable. - */ -void sha_256_write(struct Sha_256 *sha_256, const uint8_t *data, size_t len); - -/* - * @brief Conclude a SHA-256 streaming calculation, making the hash value available. - * @param sha_256 A pointer to a previously initialized SHA-256 structure. - * @return Pointer to the hash array, where the result is delivered. - * - * @note After this function has been invoked, the result is available in the hash buffer that initially was provided. A - * pointer to the hash value is returned for convenience, but you should feel free to ignore it: it is simply a pointer - * to the first byte of your initially provided hash array. - * - * @note If the passed pointer is NULL, the results are unpredictable. - * - * @note Invoking this function for a calculation with no data (the writing function has never been invoked, or it only - * has been invoked with empty data) is legal. It will calculate the SHA-256 value of the empty string. - */ -uint8_t *sha_256_close(struct Sha_256 *sha_256); - -#ifdef __cplusplus -} -#endif - -#endif \ No newline at end of file diff --git a/src/ShaTests/nerdSHA256.cpp b/src/ShaTests/nerdSHA256.cpp new file mode 100644 index 00000000..dfa7de77 --- /dev/null +++ b/src/ShaTests/nerdSHA256.cpp @@ -0,0 +1,467 @@ +#define NDEBUG +#include +#include +#include + +//#include +#include +#include + +#include "nerdSHA256.h" +#include +#include + +#define HASH_SIZE 32 + +IRAM_ATTR static inline uint32_t rotlFixed(uint32_t x, uint32_t y) + { + return (x << y) | (x >> (sizeof(y) * 8 - y)); + } +IRAM_ATTR static inline uint32_t rotrFixed(uint32_t x, uint32_t y) + { + return (x >> y) | (x << (sizeof(y) * 8 - y)); + } +/* SHA256 math based on specification */ +#define Ch(x,y,z) ((z) ^ ((x) & ((y) ^ (z)))) +#define Maj(x,y,z) ((((x) | (y)) & (z)) | ((x) & (y))) + +#define R(x, n) (((x) & 0xFFFFFFFFU) >> (n)) + +#define S(x, n) rotrFixed(x, n) +#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) +#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) +#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) +#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + +#define a(i) S[(0-(i)) & 7] +#define b(i) S[(1-(i)) & 7] +#define c(i) S[(2-(i)) & 7] +#define d(i) S[(3-(i)) & 7] +#define e(i) S[(4-(i)) & 7] +#define f(i) S[(5-(i)) & 7] +#define g(i) S[(6-(i)) & 7] +#define h(i) S[(7-(i)) & 7] + +#define XTRANSFORM(S, D) Transform_Sha256((S),(D)) +#define XMEMCPY(d,s,l) memcpy((d),(s),(l)) +#define XMEMSET(b,c,l) memset((b),(c),(l)) + +/* SHA256 version that keeps all data in registers */ +#define SCHED1(j) (W[j] = *((uint32_t*)&data[j*sizeof(uint32_t)])) +#define SCHED(j) ( \ + W[ j & 15] += \ + Gamma1(W[(j-2) & 15])+ \ + W[(j-7) & 15] + \ + Gamma0(W[(j-15) & 15]) \ + ) + +#define RND1(j) \ + t0 = h(j) + Sigma1(e(j)) + Ch(e(j), f(j), g(j)) + K[i+j] + SCHED1(j); \ + t1 = Sigma0(a(j)) + Maj(a(j), b(j), c(j)); \ + d(j) += t0; \ + h(j) = t0 + t1 +#define RNDN(j) \ + t0 = h(j) + Sigma1(e(j)) + Ch(e(j), f(j), g(j)) + K[i+j] + SCHED(j); \ + t1 = Sigma0(a(j)) + Maj(a(j), b(j), c(j)); \ + d(j) += t0; \ + h(j) = t0 + t1 + +//DRAM_ATTR static const uint32_t K[] = { +DRAM_ATTR static const uint32_t K[64] = { + 0x428A2F98L, 0x71374491L, 0xB5C0FBCFL, 0xE9B5DBA5L, 0x3956C25BL, + 0x59F111F1L, 0x923F82A4L, 0xAB1C5ED5L, 0xD807AA98L, 0x12835B01L, + 0x243185BEL, 0x550C7DC3L, 0x72BE5D74L, 0x80DEB1FEL, 0x9BDC06A7L, + 0xC19BF174L, 0xE49B69C1L, 0xEFBE4786L, 0x0FC19DC6L, 0x240CA1CCL, + 0x2DE92C6FL, 0x4A7484AAL, 0x5CB0A9DCL, 0x76F988DAL, 0x983E5152L, + 0xA831C66DL, 0xB00327C8L, 0xBF597FC7L, 0xC6E00BF3L, 0xD5A79147L, + 0x06CA6351L, 0x14292967L, 0x27B70A85L, 0x2E1B2138L, 0x4D2C6DFCL, + 0x53380D13L, 0x650A7354L, 0x766A0ABBL, 0x81C2C92EL, 0x92722C85L, + 0xA2BFE8A1L, 0xA81A664BL, 0xC24B8B70L, 0xC76C51A3L, 0xD192E819L, + 0xD6990624L, 0xF40E3585L, 0x106AA070L, 0x19A4C116L, 0x1E376C08L, + 0x2748774CL, 0x34B0BCB5L, 0x391C0CB3L, 0x4ED8AA4AL, 0x5B9CCA4FL, + 0x682E6FF3L, 0x748F82EEL, 0x78A5636FL, 0x84C87814L, 0x8CC70208L, + 0x90BEFFFAL, 0xA4506CEBL, 0xBEF9A3F7L, 0xC67178F2L + }; + +IRAM_ATTR static int Transform_Sha256(nerd_sha256* sha256, const uint8_t* data) +{ + uint32_t S[8], t0, t1; + int i; + uint32_t W[NERD_BLOCK_SIZE/sizeof(uint32_t)]; + + /* Copy digest to working vars */ + S[0] = sha256->digest[0]; + S[1] = sha256->digest[1]; + S[2] = sha256->digest[2]; + S[3] = sha256->digest[3]; + S[4] = sha256->digest[4]; + S[5] = sha256->digest[5]; + S[6] = sha256->digest[6]; + S[7] = sha256->digest[7]; + + i = 0; + RND1( 0); RND1( 1); RND1( 2); RND1( 3); + RND1( 4); RND1( 5); RND1( 6); RND1( 7); + RND1( 8); RND1( 9); RND1(10); RND1(11); + RND1(12); RND1(13); RND1(14); RND1(15); + /* 64 operations, partially loop unrolled */ + for (i = 16; i < 64; i += 16) { + RNDN( 0); RNDN( 1); RNDN( 2); RNDN( 3); + RNDN( 4); RNDN( 5); RNDN( 6); RNDN( 7); + RNDN( 8); RNDN( 9); RNDN(10); RNDN(11); + RNDN(12); RNDN(13); RNDN(14); RNDN(15); + } + + /* Add the working vars back into digest */ + sha256->digest[0] += S[0]; + sha256->digest[1] += S[1]; + sha256->digest[2] += S[2]; + sha256->digest[3] += S[3]; + sha256->digest[4] += S[4]; + sha256->digest[5] += S[5]; + sha256->digest[6] += S[6]; + sha256->digest[7] += S[7]; + + return 0; +} + +IRAM_ATTR static uint32_t ByteReverseWord32(uint32_t value){ + value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); + return rotlFixed(value, 16U); +} + +IRAM_ATTR static void ByteReverseWords(uint32_t* out, const uint32_t* in, uint32_t byteCount) +{ + uint32_t count, i; + count = byteCount/(uint32_t)sizeof(uint32_t); + for (i = 0; i < count; i++) out[i] = ByteReverseWord32(in[i]); +} + + +IRAM_ATTR static int nerd_update(nerd_sha256* sha256, uint8_t* data, uint32_t len) +{ + int ret = 0; + uint32_t blocksLen; + uint8_t* local; + + //ShaUpdate + uint32_t tmp = sha256->loLen; + if ((sha256->loLen += len) < tmp) { + sha256->hiLen++; /* carry low to high */ + } + + local = (uint8_t*)sha256->buffer; + + /* process any remainder from previous operation */ + if (sha256->buffLen > 0) { + blocksLen = min(len, NERD_BLOCK_SIZE - sha256->buffLen); + XMEMCPY(&local[sha256->buffLen], data, blocksLen); + + sha256->buffLen += blocksLen; + data += blocksLen; + len -= blocksLen; + + if (sha256->buffLen == NERD_BLOCK_SIZE) { + + ByteReverseWords(sha256->buffer, sha256->buffer, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(sha256, (const uint8_t*)local); + + if (ret == 0) + sha256->buffLen = 0; + else + len = 0; /* error */ + } + } + + /* process blocks */ + while (len >= NERD_BLOCK_SIZE) { + uint32_t* local32 = sha256->buffer; + XMEMCPY(local32, data, NERD_BLOCK_SIZE); + + data += NERD_BLOCK_SIZE; + len -= NERD_BLOCK_SIZE; + + ByteReverseWords(local32, local32, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(sha256, (const uint8_t*)local32); + + if (ret != 0) + break; + } + /* save remainder */ + if (ret == 0 && len > 0) { + XMEMCPY(local, data, len); + sha256->buffLen = len; + } + + return ret; +} + +IRAM_ATTR static int nerd_finishSHA(nerd_sha256* sha256, uint8_t* hash){ + + int ret; + uint8_t* local; + + local = (uint8_t*)sha256->buffer; + local[sha256->buffLen++] = 0x80; // add 1 + //Padd with zeros + if (sha256->buffLen > NERD_PAD_SIZE) { + + XMEMSET(&local[sha256->buffLen], 0, NERD_BLOCK_SIZE - sha256->buffLen); + sha256->buffLen += NERD_BLOCK_SIZE - sha256->buffLen; + + ByteReverseWords(sha256->buffer, sha256->buffer, NERD_BLOCK_SIZE); + XTRANSFORM(sha256, (const uint8_t*)local); + + sha256->buffLen = 0; + } + + XMEMSET(&local[sha256->buffLen], 0, NERD_PAD_SIZE - sha256->buffLen); + + // put lengths in bits + sha256->hiLen = (sha256->loLen >> (8 * sizeof(sha256->loLen) - 3)) + (sha256->hiLen << 3); + sha256->loLen = sha256->loLen << 3; + + ByteReverseWords(sha256->buffer, sha256->buffer, NERD_BLOCK_SIZE); + + // ! length ordering dependent on digest endian type ! + XMEMCPY(&local[NERD_PAD_SIZE], &sha256->hiLen, sizeof(uint32_t)); + XMEMCPY(&local[NERD_PAD_SIZE + sizeof(uint32_t)], &sha256->loLen, sizeof(uint32_t)); + + XTRANSFORM(sha256, (const uint8_t*)local); + + ByteReverseWords(sha256->digest, sha256->digest, NERD_DIGEST_SIZE); + + //Copy temp hash + XMEMCPY(hash, sha256->digest, NERD_DIGEST_SIZE); + + return 0; +} + +IRAM_ATTR int nerd_midstate(nerd_sha256* sha256, uint8_t* data, uint32_t len) +{ + int ret = 0; + uint32_t blocksLen; + uint8_t* local; + + //Init SHA context + XMEMSET(sha256->digest, 0, sizeof(sha256->digest)); + sha256->digest[0] = 0x6A09E667L; + sha256->digest[1] = 0xBB67AE85L; + sha256->digest[2] = 0x3C6EF372L; + sha256->digest[3] = 0xA54FF53AL; + sha256->digest[4] = 0x510E527FL; + sha256->digest[5] = 0x9B05688CL; + sha256->digest[6] = 0x1F83D9ABL; + sha256->digest[7] = 0x5BE0CD19L; + + sha256->buffLen = 0; + sha256->loLen = 0; + sha256->hiLen = 0; + //endINIT Sha contexxt + + nerd_update(sha256,data,len); + + return 0; +} + +/* +IRAM_ATTR int nerd_double_sha(nerd_sha256* midstate, uint8_t* data, uint8_t* doubleHash) +{ + nerd_sha256 sha256; + int ret = 0; + uint8_t hash[32]; + + //Copy current context + XMEMCPY(&sha256, midstate, sizeof(nerd_sha256)); + + // ------ First SHA ------ + nerd_update(&sha256,data,16); //Pending 16 bytes from 80 of blockheader + nerd_finishSHA(&sha256,hash); + + // ------ Second SHA ------ + //Init SHA context + XMEMSET(sha256.digest, 0, sizeof(sha256.digest)); + sha256.digest[0] = 0x6A09E667L; + sha256.digest[1] = 0xBB67AE85L; + sha256.digest[2] = 0x3C6EF372L; + sha256.digest[3] = 0xA54FF53AL; + sha256.digest[4] = 0x510E527FL; + sha256.digest[5] = 0x9B05688CL; + sha256.digest[6] = 0x1F83D9ABL; + sha256.digest[7] = 0x5BE0CD19L; + + sha256.buffLen = 0; + sha256.loLen = 0; + sha256.hiLen = 0; + //endINIT Sha context + nerd_update(&sha256,hash,32); + nerd_finishSHA(&sha256,doubleHash); + + return 0; +} +*/ + +IRAM_ATTR int nerd_double_sha(nerd_sha256* midstate, uint8_t* data, uint8_t* doubleHash) +{ + IRAM_DATA_ATTR nerd_sha256 sha256; + //nerd_sha256 sha256_2; + int ret = 0; + uint32_t blocksLen; + uint8_t* local; + uint8_t* local2; + uint8_t tmpHash[32]; + uint8_t* hash; + + //Copy current context + XMEMCPY(&sha256, midstate, sizeof(nerd_sha256)); + + // ----- 1rst SHA ------------ + //*********** ShaUpdate *********** + uint32_t len = 16; //Pending bytes to make the sha256 + uint32_t tmp = sha256.loLen; + if ((sha256.loLen += len) < tmp) { + sha256.hiLen++; + } + + local = (uint8_t*)sha256.buffer; + // save remainder + if (ret == 0 && len > 0) { + XMEMCPY(local, data, len); + sha256.buffLen = len; + } + //*********** end update *********** + + //*********** Init SHA_finish *********** + + local[sha256.buffLen++] = 0x80; // add 1 + + XMEMSET(&local[sha256.buffLen], 0, NERD_PAD_SIZE - sha256.buffLen); + + // put lengths in bits + sha256.hiLen = (sha256.loLen >> (8 * sizeof(sha256.loLen) - 3)) + (sha256.hiLen << 3); + sha256.loLen = sha256.loLen << 3; + + ByteReverseWords(sha256.buffer, sha256.buffer, NERD_BLOCK_SIZE); + + // ! length ordering dependent on digest endian type ! + XMEMCPY(&local[NERD_PAD_SIZE], &sha256.hiLen, sizeof(uint32_t)); + XMEMCPY(&local[NERD_PAD_SIZE + sizeof(uint32_t)], &sha256.loLen, sizeof(uint32_t)); + + XTRANSFORM(&sha256, (const uint8_t*)local); + + ByteReverseWords((uint32_t* )tmpHash, sha256.digest, NERD_DIGEST_SIZE); + + hash = tmpHash; + + //*********** end SHA_finish *********** + + // ----- 2nd SHA ------------ + //Init SHA context again + XMEMSET(sha256.digest, 0, sizeof(sha256.digest)); + sha256.digest[0] = 0x6A09E667L; + sha256.digest[1] = 0xBB67AE85L; + sha256.digest[2] = 0x3C6EF372L; + sha256.digest[3] = 0xA54FF53AL; + sha256.digest[4] = 0x510E527FL; + sha256.digest[5] = 0x9B05688CL; + sha256.digest[6] = 0x1F83D9ABL; + sha256.digest[7] = 0x5BE0CD19L; + + sha256.buffLen = 0; + sha256.loLen = 0; + sha256.hiLen = 0; + //endINIT Sha context + + //*********** ShaUpdate *********** + len = 32; //Current hash size to make the 2nd sha256 + tmp = sha256.loLen; + if ((sha256.loLen += len) < tmp) { + sha256.hiLen++; + } + + local2 = (uint8_t*)sha256.buffer; + + // process any remainder from previous operation + if (sha256.buffLen > 0) { + blocksLen = min(len, NERD_BLOCK_SIZE - sha256.buffLen); + XMEMCPY(&local2[sha256.buffLen], hash, blocksLen); + + sha256.buffLen += blocksLen; + hash += blocksLen; + len -= blocksLen; + + if (sha256.buffLen == NERD_BLOCK_SIZE) { + + ByteReverseWords(sha256.buffer, sha256.buffer, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(&sha256, (const uint8_t*)local2); + + if (ret == 0) + sha256.buffLen = 0; + else + len = 0; // error + } + } + + // process blocks + while (len >= NERD_BLOCK_SIZE) { + uint32_t* local32 = sha256.buffer; + XMEMCPY(local32, hash, NERD_BLOCK_SIZE); + + hash += NERD_BLOCK_SIZE; + len -= NERD_BLOCK_SIZE; + + ByteReverseWords(local32, local32, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(&sha256, (const uint8_t*)local32); + + if (ret != 0) + break; + } + // save remainder + if (ret == 0 && len > 0) { + XMEMCPY(local2, hash, len); + sha256.buffLen = len; + } + //*********** end update *********** + + //*********** Init SHA_finish *********** + + //local2 = (uint8_t*)sha256.buffer; + local2[sha256.buffLen++] = 0x80; // add 1 + //local2[33] = 0x80; // add 1 + + //Padd with zeros + + if (sha256.buffLen > NERD_PAD_SIZE) { + + XMEMSET(&local2[sha256.buffLen], 0, NERD_BLOCK_SIZE - sha256.buffLen); + sha256.buffLen += NERD_BLOCK_SIZE - sha256.buffLen; + + //ByteReverseWords(sha256_2.buffer, sha256_2.buffer, NERD_BLOCK_SIZE); + XTRANSFORM(&sha256, (const uint8_t*)local2); + + sha256.buffLen = 0; + } + + XMEMSET(&local2[sha256.buffLen], 0, NERD_PAD_SIZE - sha256.buffLen); + + // put lengths in bits + sha256.hiLen = (sha256.loLen >> (8 * sizeof(sha256.loLen) - 3)) + (sha256.hiLen << 3); + sha256.loLen = sha256.loLen << 3; + + ByteReverseWords(sha256.buffer, sha256.buffer, NERD_BLOCK_SIZE); + + // ! length ordering dependent on digest endian type ! + XMEMCPY(&local2[NERD_PAD_SIZE], &sha256.hiLen, sizeof(uint32_t)); + XMEMCPY(&local2[NERD_PAD_SIZE + sizeof(uint32_t)], &sha256.loLen, sizeof(uint32_t)); + + XTRANSFORM(&sha256, (const uint8_t*)local2); + + ByteReverseWords((uint32_t*)doubleHash, sha256.digest, NERD_DIGEST_SIZE); + + return 0; +} + diff --git a/src/ShaTests/nerdSHA256.h b/src/ShaTests/nerdSHA256.h new file mode 100644 index 00000000..c4b866ae --- /dev/null +++ b/src/ShaTests/nerdSHA256.h @@ -0,0 +1,26 @@ +#ifndef nerdSHA256_H_ +#define nerdSHA256_H_ + +#include +#include +#include + +#define NERD_DIGEST_SIZE 32 +#define NERD_BLOCK_SIZE 64 +#define NERD_PAD_SIZE 56 + +struct nerd_sha256 { + uint32_t digest[NERD_DIGEST_SIZE / sizeof(uint32_t)]; + uint32_t buffer[NERD_BLOCK_SIZE / sizeof(uint32_t)]; + uint32_t buffLen; /* in bytes */ + uint32_t loLen; /* length in bytes */ + uint32_t hiLen; /* length in bytes */ + void* heap; +}; + +/* Calculate midstate */ +IRAM_ATTR int nerd_midstate(nerd_sha256* sha256, uint8_t* data, uint32_t len); + +IRAM_ATTR int nerd_double_sha(nerd_sha256* midstate, uint8_t* data, uint8_t* doubleHash); + +#endif /* nerdSHA256_H_ */ \ No newline at end of file diff --git a/src/mining.cpp b/src/mining.cpp index f272b956..456f0e8c 100644 --- a/src/mining.cpp +++ b/src/mining.cpp @@ -1,8 +1,10 @@ #include #include #include +#include #include // Graphics and font library for ILI9341 driver chip #include +//#include "ShaTests/nerdSHA256.h" #include "media/Free_Fonts.h" #include "media/images.h" #include "OpenFontRender.h" @@ -110,12 +112,14 @@ void runStratumWorker(void *name) { // connect to pool - float currentPoolDifficulty = atof(DEFAULT_DIFFICULTY); + double currentPoolDifficulty = DEFAULT_DIFFICULTY; while(true) { if(WiFi.status() != WL_CONNECTED){ - vTaskDelay(1000 / portTICK_PERIOD_MS); + // WiFi is disconnected, so reconnect now + WiFi.reconnect(); + vTaskDelay(5000 / portTICK_PERIOD_MS); continue; } @@ -138,6 +142,8 @@ void runStratumWorker(void *name) { if(!isMinerSuscribed){ + //Stop miner current jobs + mMiner.inRun = false; mWorker = init_mining_subscribe(); // STEP 1: Pool server connection (SUBSCRIBE) @@ -194,6 +200,7 @@ void runStratumWorker(void *name) { case MINING_SET_DIFFICULTY: parse_mining_set_difficulty(line, currentPoolDifficulty); mMiner.poolDifficulty = currentPoolDifficulty; break; + case STRATUM_SUCCESS: Serial.println(" Parsed JSON: Success"); break; default: Serial.println(" Parsed JSON: unknown"); break; } @@ -210,12 +217,12 @@ void runStratumWorker(void *name) { //This works only with one thread, TODO -> Class or miner_data for each thread -#include "shaTests/jadeSHA256.h" -#include "shaTests/customSHA256.h" -#include "mbedtls/sha256.h" -void runMiner(void * name){ - unsigned long nonce; - unsigned long max_nonce; +//#include "shaTests/jadeSHA256.h" +//#include "shaTests/customSHA256.h" +//#include "mbedtls/sha256.h" +void runMiner(void * task_id) { + + unsigned int miner_id = (uint32_t)task_id; while(1){ @@ -226,26 +233,22 @@ void runMiner(void * name){ } vTaskDelay(10 / portTICK_PERIOD_MS); //Small delay to join both mining threads - if(mMiner.newJob) { + if(mMiner.newJob) mMiner.newJob = false; //Clear newJob flag - nonce = 0; - max_nonce = MAX_NONCE; - } - else if(mMiner.newJob2){ + else if(mMiner.newJob2) mMiner.newJob2 = false; //Clear newJob flag - nonce = TARGET_NONCE - MAX_NONCE; - max_nonce = TARGET_NONCE; - } mMiner.inRun = true; //Set inRun flag //Prepare Premining data Sha256 midstate[32]; - unsigned char hash[32]; + //nerd_sha256 nerdMidstate; + uint8_t hash[32]; Sha256 sha256; //Calcular midstate WOLF wc_InitSha256(midstate); wc_Sha256Update(midstate, mMiner.bytearray_blockheader, 64); + //nerd_midstate(&nerdMidstate, mMiner.bytearray_blockheader, 64); /*Serial.println("Blockheader:"); @@ -258,11 +261,23 @@ void runMiner(void * name){ Serial.println(""); */ // search a valid nonce + unsigned long nonce = TARGET_NONCE - MAX_NONCE; + // split up odd/even nonces between miner tasks + nonce += miner_id; uint32_t startT = micros(); - unsigned char *header64 = mMiner.bytearray_blockheader + 64; + unsigned char *header64; + // each miner thread needs to track its own blockheader template + memcpy(mMiner.bytearray_blockheader2, &mMiner.bytearray_blockheader, 80); + if (miner_id == 0) + header64 = mMiner.bytearray_blockheader + 64; + else + header64 = mMiner.bytearray_blockheader2 + 64; Serial.println(">>> STARTING TO HASH NONCES"); while(true) { - memcpy(mMiner.bytearray_blockheader + 76, &nonce, 4); + if (miner_id == 0) + memcpy(mMiner.bytearray_blockheader + 76, &nonce, 4); + else + memcpy(mMiner.bytearray_blockheader2 + 76, &nonce, 4); //Con midstate // Primer SHA-256 @@ -273,6 +288,7 @@ void runMiner(void * name){ // Segundo SHA-256 wc_Sha256Update(&sha256, hash, 32); wc_Sha256Final(&sha256, hash); + //nerd_double_sha(&nerdMidstate, header64, hash); /*for (size_t i = 0; i < 32; i++) Serial.printf("%02x", hash[i]); @@ -283,11 +299,15 @@ void runMiner(void * name){ Serial.println(""); */ hashes++; - if (nonce++> max_nonce) break; //exit + if (nonce > TARGET_NONCE) break; //exit if(!mMiner.inRun) { Serial.println ("MINER WORK ABORTED >> waiting new job"); break;} // check if 16bit share - if(hash[31] !=0 || hash[30] !=0) continue; + if(hash[31] !=0 || hash[30] !=0) { + // increment nonce + nonce += 2; + continue; + } halfshares++; //Check target to submit @@ -304,26 +324,40 @@ void runMiner(void * name){ Serial.print(" - TX SHARE: "); for (size_t i = 0; i < 32; i++) Serial.printf("%02x", hash[i]); - Serial.println(""); + #ifdef DEBUG_MINING + Serial.println(""); + Serial.print(" - Current nonce: "); Serial.println(nonce); + Serial.print(" - Current block header: "); + for (size_t i = 0; i < 80; i++) { + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); + } + #endif + Serial.println(""); mLastTXtoPool = millis(); } // check if 32bit share - if(hash[29] !=0 || hash[28] !=0) continue; + if(hash[29] !=0 || hash[28] !=0) { + // increment nonce + nonce += 2; + continue; + } shares++; // check if valid header if(checkValid(hash, mMiner.bytearray_target)){ - Serial.printf("[WORKER] %s CONGRATULATIONS! Valid completed with nonce: %d | 0x%x\n", (char *)name, nonce, nonce); + Serial.printf("[WORKER] %d CONGRATULATIONS! Valid block found with nonce: %d | 0x%x\n", miner_id, nonce, nonce); valids++; - Serial.printf("[WORKER] %s Submiting work valid!\n", (char *)name); + Serial.printf("[WORKER] %d Submitted work valid!\n", miner_id); // STEP 3: Submit mining job tx_mining_submit(client, mWorker, mJob, nonce); client.stop(); // exit nonce = MAX_NONCE; break; - } + } + // increment nonce + nonce += 2; } // exit if found a valid result or nonce > MAX_NONCE wc_Sha256Free(&sha256); @@ -338,6 +372,8 @@ void runMiner(void * name){ } uint32_t duration = micros() - startT; + if (esp_task_wdt_reset() == ESP_OK) + Serial.print(">>> Resetting watchdog timer"); } } diff --git a/src/mining.h b/src/mining.h index 742b3e69..4cb28f5f 100644 --- a/src/mining.h +++ b/src/mining.h @@ -5,7 +5,7 @@ // Mining #define MAX_NONCE 5000000U #define TARGET_NONCE 471136297U -#define DEFAULT_DIFFICULTY "1e-9" +#define DEFAULT_DIFFICULTY 1e-4 #define KEEPALIVE_TIME_ms 30000 #define POOLINACTIVITY_TIME_ms 60000 @@ -21,7 +21,8 @@ typedef struct{ uint8_t bytearray_pooltarget[32]; uint8_t merkle_result[32]; uint8_t bytearray_blockheader[80]; - float poolDifficulty; + uint8_t bytearray_blockheader2[80]; + double poolDifficulty; bool inRun; bool newJob; bool newJob2; diff --git a/src/stratum.cpp b/src/stratum.cpp index dacaaaf5..00d72a4b 100644 --- a/src/stratum.cpp +++ b/src/stratum.cpp @@ -142,8 +142,13 @@ stratum_method parse_mining_method(String line) if (error || checkError(doc)) return STRATUM_PARSE_ERROR; - if (!doc.containsKey("method")) return STRATUM_UNKNOWN; - + if (!doc.containsKey("method")) { + // "error":null means success + if (doc["error"].isNull()) + return STRATUM_SUCCESS; + else + return STRATUM_UNKNOWN; + } stratum_method result = STRATUM_UNKNOWN; if (strcmp("mining.notify", (const char*) doc["method"]) == 0) { @@ -176,15 +181,15 @@ bool parse_mining_notify(String line, mining_job& mJob) mJob.clean_jobs = doc["params"][8]; //bool #ifdef DEBUG_MINING - Serial.print(" job_id: "); Serial.println(job_id); - Serial.print(" prevhash: "); Serial.println(prev_block_hash); - Serial.print(" coinb1: "); Serial.println(coinb1); - Serial.print(" coinb2: "); Serial.println(coinb2); - Serial.print(" merkle_branch size: "); Serial.println(merkle_branches.size()); - Serial.print(" version: "); Serial.println(version); - Serial.print(" nbits: "); Serial.println(nbits); - Serial.print(" ntime: "); Serial.println(ntime); - Serial.print(" clean_jobs: "); Serial.println(clean_jobs); + Serial.print(" job_id: "); Serial.println(mJob.job_id); + Serial.print(" prevhash: "); Serial.println(mJob.prev_block_hash); + Serial.print(" coinb1: "); Serial.println(mJob.coinb1); + Serial.print(" coinb2: "); Serial.println(mJob.coinb2); + Serial.print(" merkle_branch size: "); Serial.println(mJob.merkle_branch.size()); + Serial.print(" version: "); Serial.println(mJob.version); + Serial.print(" nbits: "); Serial.println(mJob.nbits); + Serial.print(" ntime: "); Serial.println(mJob.ntime); + Serial.print(" clean_jobs: "); Serial.println(mJob.clean_jobs); #endif //Check if parameters where correctly received if (checkError(doc)) { @@ -216,7 +221,7 @@ bool tx_mining_submit(WiFiClient& client, mining_subscribe mWorker, mining_job m return true; } -bool parse_mining_set_difficulty(String line, float& difficulty) +bool parse_mining_set_difficulty(String line, double& difficulty) { Serial.println(" Parsing Method [SET DIFFICULTY]"); if(!verifyPayload(&line)) return false; @@ -226,22 +231,18 @@ bool parse_mining_set_difficulty(String line, float& difficulty) if (error) return false; if (!doc.containsKey("params")) return false; - Serial.print(" difficulty: "); Serial.println((float)doc["params"][0],12); - difficulty = (float)doc["params"][0]; - - #ifdef DEBUG_MINING - Serial.print(" job_id: "); Serial.println(job_id); - #endif + Serial.print(" difficulty: "); Serial.println((double)doc["params"][0],12); + difficulty = (double)doc["params"][0]; return true; } -bool tx_suggest_difficulty(WiFiClient& client, const char * difficulty) +bool tx_suggest_difficulty(WiFiClient& client, double difficulty) { char payload[BUFFER] = {0}; id = getNextId(id); - sprintf(payload, "{\"id\": %d, \"method\": \"mining.suggest_difficulty\", \"params\": [%s]}\n", id, difficulty); + sprintf(payload, "{\"id\": %d, \"method\": \"mining.suggest_difficulty\", \"params\": [%.10g]}\n", id, difficulty); Serial.print(" Sending : "); Serial.print(payload); return client.print(payload); diff --git a/src/stratum.h b/src/stratum.h index cdd47b3f..d8110e7d 100644 --- a/src/stratum.h +++ b/src/stratum.h @@ -38,6 +38,7 @@ typedef struct { } mining_job; typedef enum { + STRATUM_SUCCESS, STRATUM_UNKNOWN, STRATUM_PARSE_ERROR, MINING_NOTIFY, @@ -62,8 +63,8 @@ bool parse_mining_notify(String line, mining_job& mJob); bool tx_mining_submit(WiFiClient& client, mining_subscribe mWorker, mining_job mJob, unsigned long nonce); //Difficulty Methods -bool tx_suggest_difficulty(WiFiClient& client, const char * difficulty); -bool parse_mining_set_difficulty(String line, float& difficulty); +bool tx_suggest_difficulty(WiFiClient& client, double difficulty); +bool parse_mining_set_difficulty(String line, double& difficulty); #endif // STRATUM_API_H \ No newline at end of file diff --git a/src/utils.cpp b/src/utils.cpp index 3050dc80..8216ef72 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -83,16 +83,16 @@ double le256todouble(const void *target) double dcut64; data64 = (uint64_t *)(target + 24); - dcut64 = bswap64(*data64) * 6277101735386680763835789423207666416102355444464034512896.0; + dcut64 = *data64 * 6277101735386680763835789423207666416102355444464034512896.0; data64 = (uint64_t *)(target + 16); - dcut64 += bswap64(*data64) * 340282366920938463463374607431768211456.0; + dcut64 += *data64 * 340282366920938463463374607431768211456.0; data64 = (uint64_t *)(target + 8); - dcut64 += bswap64(*data64) * 18446744073709551616.0; + dcut64 += *data64 * 18446744073709551616.0; data64 = (uint64_t *)(target); - dcut64 += bswap64(*data64); + dcut64 += *data64; return dcut64; } @@ -100,7 +100,6 @@ double le256todouble(const void *target) double diff_from_target(void *target) { double d64, dcut64; - //reverse_bytes((uint8_t *) target, 32); d64 = truediffone; dcut64 = le256todouble(target); @@ -155,7 +154,7 @@ miner_data init_miner_data(void){ miner_data newMinerData; - newMinerData.poolDifficulty = atof(DEFAULT_DIFFICULTY); + newMinerData.poolDifficulty = DEFAULT_DIFFICULTY; newMinerData.inRun = false; newMinerData.newJob = false; @@ -201,7 +200,7 @@ miner_data calculateMiningData(mining_subscribe& mWorker, mining_job mJob){ size_t res = to_byte_array(coinbase.c_str(), str_len*2, bytearray); #ifdef DEBUG_MINING - Serial.print(" extranonce2: "); Serial.println(extranonce2); + Serial.print(" extranonce2: "); Serial.println(mWorker.extranonce2); Serial.print(" coinbase: "); Serial.println(coinbase); Serial.print(" coinbase bytes - size: "); Serial.println(res); for (size_t i = 0; i < res; i++) @@ -271,7 +270,7 @@ miner_data calculateMiningData(mining_subscribe& mWorker, mining_job mJob){ #ifdef DEBUG_MINING Serial.print(" merkle sha : "); for (size_t i = 0; i < 32; i++) - Serial.printf("%02x", merkle_result[i]); + Serial.printf("%02x", mMiner.merkle_result[i]); Serial.println(""); #endif } @@ -285,22 +284,23 @@ miner_data calculateMiningData(mining_subscribe& mWorker, mining_job mJob){ } merkle_root[65] = 0; Serial.println(""); - + // calculate blockheader // j.block_header = ''.join([j.version, j.prevhash, merkle_root, j.ntime, j.nbits]) - String blockheader = mJob.version + mJob.prev_block_hash + String(merkle_root) + mJob.ntime + mJob.nbits + "00000000"; + String blockheader = mJob.version + mJob.prev_block_hash + String(merkle_root) + mJob.ntime + mJob.nbits + "00000000"; str_len = blockheader.length()/2; //uint8_t bytearray_blockheader[str_len]; res = to_byte_array(blockheader.c_str(), str_len*2, mMiner.bytearray_blockheader); #ifdef DEBUG_MINING + Serial.println(" blockheader: "); Serial.print(blockheader); Serial.println(" blockheader bytes "); Serial.print(str_len); Serial.print(" -> "); #endif // reverse version uint8_t buff; - size_t bsize, boffset; + size_t bword, bsize, boffset; boffset = 0; bsize = 4; for (size_t j = boffset; j < boffset + (bsize/2); j++) { @@ -309,14 +309,42 @@ miner_data calculateMiningData(mining_subscribe& mWorker, mining_job mJob){ mMiner.bytearray_blockheader[2 * boffset + bsize - 1 - j] = buff; } - // reverse merkle + // reverse prev hash (4-byte word swap) + boffset = 4; + bword = 4; + bsize = 32; + for (size_t i = 1; i <= bsize / bword; i++) { + for (size_t j = boffset; j < boffset + bword / 2; j++) { + buff = mMiner.bytearray_blockheader[j]; + mMiner.bytearray_blockheader[j] = mMiner.bytearray_blockheader[2 * boffset + bword - 1 - j]; + mMiner.bytearray_blockheader[2 * boffset + bword - 1 - j] = buff; + } + boffset += bword; + } + +/* + // reverse merkle (4-byte word swap) boffset = 36; + bword = 4; bsize = 32; + for (size_t i = 1; i <= bsize / bword; i++) { + for (size_t j = boffset; j < boffset + bword / 2; j++) { + buff = mMiner.bytearray_blockheader[j]; + mMiner.bytearray_blockheader[j] = mMiner.bytearray_blockheader[2 * boffset + bword - 1 - j]; + mMiner.bytearray_blockheader[2 * boffset + bword - 1 - j] = buff; + } + boffset += bword; + } +*/ + // reverse ntime + boffset = 68; + bsize = 4; for (size_t j = boffset; j < boffset + (bsize/2); j++) { buff = mMiner.bytearray_blockheader[j]; mMiner.bytearray_blockheader[j] = mMiner.bytearray_blockheader[2 * boffset + bsize - 1 - j]; mMiner.bytearray_blockheader[2 * boffset + bsize - 1 - j] = buff; } + // reverse difficulty boffset = 72; bsize = 4; @@ -330,35 +358,35 @@ miner_data calculateMiningData(mining_subscribe& mWorker, mining_job mJob){ #ifdef DEBUG_MINING Serial.print(" >>> bytearray_blockheader : "); for (size_t i = 0; i < 4; i++) - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); Serial.println(""); Serial.print("version "); for (size_t i = 0; i < 4; i++) - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); Serial.println(""); Serial.print("prev hash "); for (size_t i = 4; i < 4+32; i++) - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); Serial.println(""); Serial.print("merkle root "); for (size_t i = 36; i < 36+32; i++) - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); Serial.println(""); - Serial.print("nbits "); + Serial.print("ntime "); for (size_t i = 68; i < 68+4; i++) - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); Serial.println(""); - Serial.print("difficulty "); + Serial.print("nbits "); for (size_t i = 72; i < 72+4; i++) - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); Serial.println(""); Serial.print("nonce "); for (size_t i = 76; i < 76+4; i++) - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); Serial.println(""); Serial.println("bytearray_blockheader: "); for (size_t i = 0; i < str_len; i++) { - Serial.printf("%02x", bytearray_blockheader[i]); + Serial.printf("%02x", mMiner.bytearray_blockheader[i]); } Serial.println(""); #endif diff --git a/src/wManager.cpp b/src/wManager.cpp index e083da5c..992292b2 100644 --- a/src/wManager.cpp +++ b/src/wManager.cpp @@ -19,8 +19,8 @@ bool shouldSaveConfig = false; // Variables to hold data from custom textboxes -char poolString[80] = "solo.ckpool.org"; -int portNumber = 3333; +char poolString[80] = "public-pool.airdns.org"; +int portNumber = 21496;//3333; char btcString[80] = "yourBtcAddress"; int GMTzone = 2; //Currently selected in spain diff --git a/test/TestHashPerformance/src/jadeSHA256.cpp b/test/TestHashPerformance/src/jadeSHA256.cpp index 4e3073e3..cd31a6e5 100644 --- a/test/TestHashPerformance/src/jadeSHA256.cpp +++ b/test/TestHashPerformance/src/jadeSHA256.cpp @@ -16,8 +16,6 @@ #define HASH_SIZE 32 -#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) - #ifndef PUT_UINT32_BE #define PUT_UINT32_BE(n, data, offset) \ { \ @@ -105,6 +103,8 @@ static const uint32_t K[] = { }; #define SHR(x, n) ((x & 0xFFFFFFFF) >> n) +//#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define ROTR(x, n) (SHR(x, n) | ((x) << (32 - (n)))) #define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) #define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) @@ -188,7 +188,12 @@ IRAM_ATTR void calc_midstate(uint8_t* buf_ptr, _sha256_context* midstate) uint32_t A[8] = { 0x6A09E667, 0xBB67AE85, 0x3C6EF372, 0xA54FF53A, 0x510E527F, 0x9B05688C, 0x1F83D9AB, 0x5BE0CD19 }; uint32_t temp1, temp2, W[64]; + uint8_t i; + /*for (i = 0; i < 16; i++) { + W[i] = GET_UINT32_BE(buf_ptr, 4 * i); + }*/ + W[0] = GET_UINT32_BE(buf_ptr, 0); W[1] = GET_UINT32_BE(buf_ptr, 4); W[2] = GET_UINT32_BE(buf_ptr, 8); @@ -205,7 +210,51 @@ IRAM_ATTR void calc_midstate(uint8_t* buf_ptr, _sha256_context* midstate) W[13] = GET_UINT32_BE(buf_ptr, 52); W[14] = GET_UINT32_BE(buf_ptr, 56); W[15] = GET_UINT32_BE(buf_ptr, 60); + + + + for (i = 0; i < 16; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], W[i+0], K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], W[i+1], K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], W[i+2], K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], W[i+3], K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], W[i+4], K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], W[i+5], K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], W[i+6], K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], W[i+7], K[i+7]); + } + + for (i = 16; i < 64; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], R(i+0), K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], R(i+1), K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], R(i+2), K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], R(i+3), K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], R(i+4), K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], R(i+5), K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], R(i+6), K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], R(i+7), K[i+7]); + } + for (i = 0; i < 8; i++) { + midstate->state[i] += A[i]; + } + /* P(A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[0], K[0]); P(A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], W[1], K[1]); P(A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], W[2], K[2]); @@ -271,15 +320,16 @@ IRAM_ATTR void calc_midstate(uint8_t* buf_ptr, _sha256_context* midstate) P(A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], R(62), K[62]); P(A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], R(63), K[63]); - midstate->state[0] = A[0];// 0x6A09E667 + A[0]; - midstate->state[1] = A[1];// 0xBB67AE85 + A[1]; - midstate->state[2] = A[2];// 0x3C6EF372 + A[2]; - midstate->state[3] = A[3];// 0xA54FF53A + A[3]; - midstate->state[4] = A[4];// 0x510E527F + A[4]; - midstate->state[5] = A[5];// 0x9B05688C + A[5]; - midstate->state[6] = A[6];// 0x1F83D9AB + A[6]; - midstate->state[7] = A[7];// 0x5BE0CD19 + A[7]; - midstate->buffer[16] = 0x80; + midstate->state[0] = 0x6A09E667 + A[0]; + midstate->state[1] = 0xBB67AE85 + A[1]; + midstate->state[2] = 0x3C6EF372 + A[2]; + midstate->state[3] = 0xA54FF53A + A[3]; + midstate->state[4] = 0x510E527F + A[4]; + midstate->state[5] = 0x9B05688C + A[5]; + midstate->state[6] = 0x1F83D9AB + A[6]; + midstate->state[7] = 0x5BE0CD19 + A[7]; + */ + //midstate->buffer[16] = 0x80; memcpy(midstate->buffer, buf_ptr + 64, 12); } @@ -289,18 +339,59 @@ IRAM_ATTR bool make_double_sha(_sha256_context* midstate) uint8_t temp3, temp4; uint32_t W[64] = { GET_UINT32_BE(midstate->buffer, 0), GET_UINT32_BE(midstate->buffer, 4), - GET_UINT32_BE(midstate->buffer, 8), GET_UINT32_BE(midstate->buffer, 12), 2147483648, 0, 0, 0, 0, 0, 0, 0, 0, 0, + GET_UINT32_BE(midstate->buffer, 8), GET_UINT32_BE(midstate->buffer, 12), 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 640, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; uint32_t A[8] = { midstate->state[0], midstate->state[1], midstate->state[2], midstate->state[3], midstate->state[4], midstate->state[5], midstate->state[6], midstate->state[7] }; -//2147483648 +//0x80000000 union { uint32_t num; uint8_t b[4]; } u; uint8_t* p = NULL; + uint8_t i; + + for (i = 0; i < 16; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], W[i+0], K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], W[i+1], K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], W[i+2], K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], W[i+3], K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], W[i+4], K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], W[i+5], K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], W[i+6], K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], W[i+7], K[i+7]); + } + + for (i = 16; i < 64; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], R(i+0), K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], R(i+1), K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], R(i+2), K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], R(i+3), K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], R(i+4), K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], R(i+5), K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], R(i+6), K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], R(i+7), K[i+7]); + } + + /* P(A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[0], K[0]); P(A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], W[1], K[1]); P(A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], W[2], K[2]); @@ -365,6 +456,7 @@ IRAM_ATTR bool make_double_sha(_sha256_context* midstate) P(A[3], A[4], A[5], A[6], A[7], A[0], A[1], A[2], R(61), K[61]); P(A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], R(62), K[62]); P(A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], R(63), K[63]); + */ PUT_UINT32_BE(midstate->state[0] + A[0], midstate->buffer, 0); PUT_UINT32_BE(midstate->state[1] + A[1], midstate->buffer, 4); @@ -403,6 +495,45 @@ IRAM_ATTR bool make_double_sha(_sha256_context* midstate) W[14] = 0; W[15] = 256; + for (i = 0; i < 16; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], W[i+0], K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], W[i+1], K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], W[i+2], K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], W[i+3], K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], W[i+4], K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], W[i+5], K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], W[i+6], K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], W[i+7], K[i+7]); + } + + for (i = 16; i < 64; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], R(i+0), K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], R(i+1), K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], R(i+2), K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], R(i+3), K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], R(i+4), K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], R(i+5), K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], R(i+6), K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], R(i+7), K[i+7]); + } + + /* P(A[0], A[1], A[2], A[3], A[4], A[5], A[6], A[7], W[0], K[0]); P(A[7], A[0], A[1], A[2], A[3], A[4], A[5], A[6], W[1], K[1]); P(A[6], A[7], A[0], A[1], A[2], A[3], A[4], A[5], W[2], K[2]); @@ -471,7 +602,7 @@ IRAM_ATTR bool make_double_sha(_sha256_context* midstate) P(A[2], A[3], A[4], A[5], A[6], A[7], A[0], A[1], R(62), K[62]); //CHECK_BYTES(0x9B05688C, A[5], 8); P(A[1], A[2], A[3], A[4], A[5], A[6], A[7], A[0], R(63), K[63]); - + */ /*CHECK_BYTES(0x510E527F, A[4], 12); CHECK_BYTES(0xA54FF53A, A[3], 16); CHECK_BYTES(0x3C6EF372, A[2], 20); diff --git a/test/TestHashPerformance/src/nerdSHA256.cpp b/test/TestHashPerformance/src/nerdSHA256.cpp new file mode 100644 index 00000000..39c81723 --- /dev/null +++ b/test/TestHashPerformance/src/nerdSHA256.cpp @@ -0,0 +1,567 @@ +#define NDEBUG +#include +#include +#include + +//#include +#include +#include + +#include "nerdSHA256.h" +#include +#include + +#define HASH_SIZE 32 + +IRAM_ATTR static inline uint32_t rotlFixed(uint32_t x, uint32_t y) + { + return (x << y) | (x >> (sizeof(y) * 8 - y)); + } +IRAM_ATTR static inline uint32_t rotrFixed(uint32_t x, uint32_t y) + { + return (x >> y) | (x << (sizeof(y) * 8 - y)); + } +/* SHA256 math based on specification */ +#define Ch(x,y,z) ((z) ^ ((x) & ((y) ^ (z)))) +#define Maj(x,y,z) ((((x) | (y)) & (z)) | ((x) & (y))) + +//#define R(x, n) (((x) & 0xFFFFFFFFU) >> (n)) + +#define S(x, n) rotrFixed(x, n) +#define Sigma0(x) (S(x, 2) ^ S(x, 13) ^ S(x, 22)) +#define Sigma1(x) (S(x, 6) ^ S(x, 11) ^ S(x, 25)) +#define Gamma0(x) (S(x, 7) ^ S(x, 18) ^ R(x, 3)) +#define Gamma1(x) (S(x, 17) ^ S(x, 19) ^ R(x, 10)) + +#define a(i) S[(0-(i)) & 7] +#define b(i) S[(1-(i)) & 7] +#define c(i) S[(2-(i)) & 7] +#define d(i) S[(3-(i)) & 7] +#define e(i) S[(4-(i)) & 7] +#define f(i) S[(5-(i)) & 7] +#define g(i) S[(6-(i)) & 7] +#define h(i) S[(7-(i)) & 7] + +#define XTRANSFORM(S, D) Transform_Sha256((S),(D)) +#define XMEMCPY(d,s,l) memcpy((d),(s),(l)) +#define XMEMSET(b,c,l) memset((b),(c),(l)) + +/* SHA256 version that keeps all data in registers */ +#define SCHED1(j) (W[j] = *((uint32_t*)&data[j*sizeof(uint32_t)])) +#define SCHED(j) ( \ + W[ j & 15] += \ + Gamma1(W[(j-2) & 15])+ \ + W[(j-7) & 15] + \ + Gamma0(W[(j-15) & 15]) \ + ) + +#define RND1(j) \ + t0 = h(j) + Sigma1(e(j)) + Ch(e(j), f(j), g(j)) + K[i+j] + SCHED1(j); \ + t1 = Sigma0(a(j)) + Maj(a(j), b(j), c(j)); \ + d(j) += t0; \ + h(j) = t0 + t1 +#define RNDN(j) \ + t0 = h(j) + Sigma1(e(j)) + Ch(e(j), f(j), g(j)) + K[i+j] + SCHED(j); \ + t1 = Sigma0(a(j)) + Maj(a(j), b(j), c(j)); \ + d(j) += t0; \ + h(j) = t0 + t1 + +#define SHR(x, n) ((x & 0xFFFFFFFF) >> n) +//#define ROTR(x, n) ((x >> n) | (x << ((sizeof(x) << 3) - n))) +#define ROTR(x, n) (SHR(x, n) | ((x) << (32 - (n)))) + +#define S0(x) (ROTR(x, 7) ^ ROTR(x, 18) ^ SHR(x, 3)) +#define S1(x) (ROTR(x, 17) ^ ROTR(x, 19) ^ SHR(x, 10)) + +#define S2(x) (ROTR(x, 2) ^ ROTR(x, 13) ^ ROTR(x, 22)) +#define S3(x) (ROTR(x, 6) ^ ROTR(x, 11) ^ ROTR(x, 25)) + +#define F0(x, y, z) ((x & y) | (z & (x | y))) +#define F1(x, y, z) (z ^ (x & (y ^ z))) + +#define R(t) (W[t] = S1(W[t - 2]) + W[t - 7] + S0(W[t - 15]) + W[t - 16]) + +#define P(a, b, c, d, e, f, g, h, x, K) \ + { \ + temp1 = h + S3(e) + F1(e, f, g) + K + x; \ + temp2 = S2(a) + F0(a, b, c); \ + d += temp1; \ + h = temp1 + temp2; \ + } +#define GET_UINT32_BE(b, i) \ + (((uint32_t)(b)[(i)] << 24) | ((uint32_t)(b)[(i) + 1] << 16) | ((uint32_t)(b)[(i) + 2] << 8) \ + | ((uint32_t)(b)[(i) + 3])) + +//DRAM_ATTR static const uint32_t K[] = { +DRAM_ATTR static const uint32_t K[64] = { + 0x428A2F98L, 0x71374491L, 0xB5C0FBCFL, 0xE9B5DBA5L, 0x3956C25BL, + 0x59F111F1L, 0x923F82A4L, 0xAB1C5ED5L, 0xD807AA98L, 0x12835B01L, + 0x243185BEL, 0x550C7DC3L, 0x72BE5D74L, 0x80DEB1FEL, 0x9BDC06A7L, + 0xC19BF174L, 0xE49B69C1L, 0xEFBE4786L, 0x0FC19DC6L, 0x240CA1CCL, + 0x2DE92C6FL, 0x4A7484AAL, 0x5CB0A9DCL, 0x76F988DAL, 0x983E5152L, + 0xA831C66DL, 0xB00327C8L, 0xBF597FC7L, 0xC6E00BF3L, 0xD5A79147L, + 0x06CA6351L, 0x14292967L, 0x27B70A85L, 0x2E1B2138L, 0x4D2C6DFCL, + 0x53380D13L, 0x650A7354L, 0x766A0ABBL, 0x81C2C92EL, 0x92722C85L, + 0xA2BFE8A1L, 0xA81A664BL, 0xC24B8B70L, 0xC76C51A3L, 0xD192E819L, + 0xD6990624L, 0xF40E3585L, 0x106AA070L, 0x19A4C116L, 0x1E376C08L, + 0x2748774CL, 0x34B0BCB5L, 0x391C0CB3L, 0x4ED8AA4AL, 0x5B9CCA4FL, + 0x682E6FF3L, 0x748F82EEL, 0x78A5636FL, 0x84C87814L, 0x8CC70208L, + 0x90BEFFFAL, 0xA4506CEBL, 0xBEF9A3F7L, 0xC67178F2L + }; + +/* +IRAM_ATTR static int Transform_Sha256(nerd_sha256* sha256, const uint8_t* buf_ptr) +{ + + uint32_t A[8] = {0 }; + + uint32_t temp1, temp2, W[64]; + int i=0; + + for (i = 0; i < 8; i++) { + A[i] = sha256->digest[i]; + } + + W[0] = GET_UINT32_BE(buf_ptr, 0); + W[1] = GET_UINT32_BE(buf_ptr, 4); + W[2] = GET_UINT32_BE(buf_ptr, 8); + W[3] = GET_UINT32_BE(buf_ptr, 12); + W[4] = GET_UINT32_BE(buf_ptr, 16); + W[5] = GET_UINT32_BE(buf_ptr, 20); + W[6] = GET_UINT32_BE(buf_ptr, 24); + W[7] = GET_UINT32_BE(buf_ptr, 28); + W[8] = GET_UINT32_BE(buf_ptr, 32); + W[9] = GET_UINT32_BE(buf_ptr, 36); + W[10] = GET_UINT32_BE(buf_ptr, 40); + W[11] = GET_UINT32_BE(buf_ptr, 44); + W[12] = GET_UINT32_BE(buf_ptr, 48); + W[13] = GET_UINT32_BE(buf_ptr, 52); + W[14] = GET_UINT32_BE(buf_ptr, 56); + W[15] = GET_UINT32_BE(buf_ptr, 60); + + for (i = 0; i < 16; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], W[i+0], K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], W[i+1], K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], W[i+2], K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], W[i+3], K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], W[i+4], K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], W[i+5], K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], W[i+6], K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], W[i+7], K[i+7]); + } + + for (i = 16; i < 64; i += 8) { + P(A[0], A[1], A[2], A[3], A[4], + A[5], A[6], A[7], R(i+0), K[i+0]); + P(A[7], A[0], A[1], A[2], A[3], + A[4], A[5], A[6], R(i+1), K[i+1]); + P(A[6], A[7], A[0], A[1], A[2], + A[3], A[4], A[5], R(i+2), K[i+2]); + P(A[5], A[6], A[7], A[0], A[1], + A[2], A[3], A[4], R(i+3), K[i+3]); + P(A[4], A[5], A[6], A[7], A[0], + A[1], A[2], A[3], R(i+4), K[i+4]); + P(A[3], A[4], A[5], A[6], A[7], + A[0], A[1], A[2], R(i+5), K[i+5]); + P(A[2], A[3], A[4], A[5], A[6], + A[7], A[0], A[1], R(i+6), K[i+6]); + P(A[1], A[2], A[3], A[4], A[5], + A[6], A[7], A[0], R(i+7), K[i+7]); + } + + for (i = 0; i < 8; i++) { + sha256->digest[i] += A[i]; + } +} +*/ + +IRAM_ATTR static int Transform_Sha256(nerd_sha256* sha256, const uint8_t* data) +{ + uint32_t S[8], t0, t1; + int i; + uint32_t W[NERD_BLOCK_SIZE/sizeof(uint32_t)]; + + // Copy digest to working vars + S[0] = sha256->digest[0]; + S[1] = sha256->digest[1]; + S[2] = sha256->digest[2]; + S[3] = sha256->digest[3]; + S[4] = sha256->digest[4]; + S[5] = sha256->digest[5]; + S[6] = sha256->digest[6]; + S[7] = sha256->digest[7]; + + i = 0; + RND1( 0); RND1( 1); RND1( 2); RND1( 3); + RND1( 4); RND1( 5); RND1( 6); RND1( 7); + RND1( 8); RND1( 9); RND1(10); RND1(11); + RND1(12); RND1(13); RND1(14); RND1(15); + // 64 operations, partially loop unrolled + for (i = 16; i < 64; i += 16) { + RNDN( 0); RNDN( 1); RNDN( 2); RNDN( 3); + RNDN( 4); RNDN( 5); RNDN( 6); RNDN( 7); + RNDN( 8); RNDN( 9); RNDN(10); RNDN(11); + RNDN(12); RNDN(13); RNDN(14); RNDN(15); + } + + // Add the working vars back into digest + sha256->digest[0] += S[0]; + sha256->digest[1] += S[1]; + sha256->digest[2] += S[2]; + sha256->digest[3] += S[3]; + sha256->digest[4] += S[4]; + sha256->digest[5] += S[5]; + sha256->digest[6] += S[6]; + sha256->digest[7] += S[7]; + + return 0; +} + +IRAM_ATTR static uint32_t ByteReverseWord32(uint32_t value){ + value = ((value & 0xFF00FF00) >> 8) | ((value & 0x00FF00FF) << 8); + return rotlFixed(value, 16U); +} + +IRAM_ATTR static void ByteReverseWords(uint32_t* out, const uint32_t* in, uint32_t byteCount) +{ + uint32_t count, i; + count = byteCount/(uint32_t)sizeof(uint32_t); + for (i = 0; i < count; i++) out[i] = ByteReverseWord32(in[i]); +} + + +IRAM_ATTR static int nerd_update(nerd_sha256* sha256, uint8_t* data, uint32_t len) +{ + int ret = 0; + uint32_t blocksLen; + uint8_t* local; + + //ShaUpdate + uint32_t tmp = sha256->loLen; + if ((sha256->loLen += len) < tmp) { + sha256->hiLen++; /* carry low to high */ + } + + local = (uint8_t*)sha256->buffer; + + /* process any remainder from previous operation */ + if (sha256->buffLen > 0) { + blocksLen = min(len, NERD_BLOCK_SIZE - sha256->buffLen); + XMEMCPY(&local[sha256->buffLen], data, blocksLen); + + sha256->buffLen += blocksLen; + data += blocksLen; + len -= blocksLen; + + if (sha256->buffLen == NERD_BLOCK_SIZE) { + + ByteReverseWords(sha256->buffer, sha256->buffer, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(sha256, (const uint8_t*)local); + + if (ret == 0) + sha256->buffLen = 0; + else + len = 0; /* error */ + } + } + + /* process blocks */ + while (len >= NERD_BLOCK_SIZE) { + uint32_t* local32 = sha256->buffer; + XMEMCPY(local32, data, NERD_BLOCK_SIZE); + + data += NERD_BLOCK_SIZE; + len -= NERD_BLOCK_SIZE; + + ByteReverseWords(local32, local32, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(sha256, (const uint8_t*)local32); + + if (ret != 0) + break; + } + /* save remainder */ + if (ret == 0 && len > 0) { + XMEMCPY(local, data, len); + sha256->buffLen = len; + } + + return ret; +} + +IRAM_ATTR static int nerd_finishSHA(nerd_sha256* sha256, uint8_t* hash){ + + int ret; + uint8_t* local; + + local = (uint8_t*)sha256->buffer; + local[sha256->buffLen++] = 0x80; // add 1 + //Padd with zeros + if (sha256->buffLen > NERD_PAD_SIZE) { + + XMEMSET(&local[sha256->buffLen], 0, NERD_BLOCK_SIZE - sha256->buffLen); + sha256->buffLen += NERD_BLOCK_SIZE - sha256->buffLen; + + ByteReverseWords(sha256->buffer, sha256->buffer, NERD_BLOCK_SIZE); + XTRANSFORM(sha256, (const uint8_t*)local); + + sha256->buffLen = 0; + } + + XMEMSET(&local[sha256->buffLen], 0, NERD_PAD_SIZE - sha256->buffLen); + + // put lengths in bits + sha256->hiLen = (sha256->loLen >> (8 * sizeof(sha256->loLen) - 3)) + (sha256->hiLen << 3); + sha256->loLen = sha256->loLen << 3; + + ByteReverseWords(sha256->buffer, sha256->buffer, NERD_BLOCK_SIZE); + + // ! length ordering dependent on digest endian type ! + XMEMCPY(&local[NERD_PAD_SIZE], &sha256->hiLen, sizeof(uint32_t)); + XMEMCPY(&local[NERD_PAD_SIZE + sizeof(uint32_t)], &sha256->loLen, sizeof(uint32_t)); + + XTRANSFORM(sha256, (const uint8_t*)local); + + ByteReverseWords(sha256->digest, sha256->digest, NERD_DIGEST_SIZE); + + //Copy temp hash + XMEMCPY(hash, sha256->digest, NERD_DIGEST_SIZE); + + return 0; +} + +IRAM_ATTR int nerd_midstate(nerd_sha256* sha256, uint8_t* data, uint32_t len) +{ + int ret = 0; + uint32_t blocksLen; + uint8_t* local; + + //Init SHA context + XMEMSET(sha256->digest, 0, sizeof(sha256->digest)); + sha256->digest[0] = 0x6A09E667L; + sha256->digest[1] = 0xBB67AE85L; + sha256->digest[2] = 0x3C6EF372L; + sha256->digest[3] = 0xA54FF53AL; + sha256->digest[4] = 0x510E527FL; + sha256->digest[5] = 0x9B05688CL; + sha256->digest[6] = 0x1F83D9ABL; + sha256->digest[7] = 0x5BE0CD19L; + + sha256->buffLen = 0; + sha256->loLen = 0; + sha256->hiLen = 0; + //endINIT Sha contexxt + + nerd_update(sha256,data,len); + + return 0; +} + +/* +IRAM_ATTR int nerd_double_sha(nerd_sha256* midstate, uint8_t* data, uint8_t* doubleHash) +{ + nerd_sha256 sha256; + int ret = 0; + uint8_t hash[32]; + + //Copy current context + XMEMCPY(&sha256, midstate, sizeof(nerd_sha256)); + + // ------ First SHA ------ + nerd_update(&sha256,data,16); //Pending 16 bytes from 80 of blockheader + nerd_finishSHA(&sha256,hash); + + // ------ Second SHA ------ + //Init SHA context + XMEMSET(sha256.digest, 0, sizeof(sha256.digest)); + sha256.digest[0] = 0x6A09E667L; + sha256.digest[1] = 0xBB67AE85L; + sha256.digest[2] = 0x3C6EF372L; + sha256.digest[3] = 0xA54FF53AL; + sha256.digest[4] = 0x510E527FL; + sha256.digest[5] = 0x9B05688CL; + sha256.digest[6] = 0x1F83D9ABL; + sha256.digest[7] = 0x5BE0CD19L; + + sha256.buffLen = 0; + sha256.loLen = 0; + sha256.hiLen = 0; + //endINIT Sha context + nerd_update(&sha256,hash,32); + nerd_finishSHA(&sha256,doubleHash); + + return 0; +} +*/ + +IRAM_ATTR int nerd_double_sha(nerd_sha256* midstate, uint8_t* data, uint8_t* doubleHash) +{ + IRAM_DATA_ATTR nerd_sha256 sha256; + //nerd_sha256 sha256_2; + int ret = 0; + uint32_t blocksLen; + uint8_t* local; + uint8_t* local2; + uint8_t tmpHash[32]; + uint8_t* hash; + + //Copy current context + XMEMCPY(&sha256, midstate, sizeof(nerd_sha256)); + + // ----- 1rst SHA ------------ + //*********** ShaUpdate *********** + uint32_t len = 16; //Pending bytes to make the sha256 + uint32_t tmp = sha256.loLen; + if ((sha256.loLen += len) < tmp) { + sha256.hiLen++; + } + + local = (uint8_t*)sha256.buffer; + // save remainder + if (ret == 0 && len > 0) { + XMEMCPY(local, data, len); + sha256.buffLen = len; + } + //*********** end update *********** + + //*********** Init SHA_finish *********** + + local[sha256.buffLen++] = 0x80; // add 1 + + XMEMSET(&local[sha256.buffLen], 0, NERD_PAD_SIZE - sha256.buffLen); + + // put lengths in bits + sha256.hiLen = (sha256.loLen >> (8 * sizeof(sha256.loLen) - 3)) + (sha256.hiLen << 3); + sha256.loLen = sha256.loLen << 3; + + ByteReverseWords(sha256.buffer, sha256.buffer, NERD_BLOCK_SIZE); + + // ! length ordering dependent on digest endian type ! + XMEMCPY(&local[NERD_PAD_SIZE], &sha256.hiLen, sizeof(uint32_t)); + XMEMCPY(&local[NERD_PAD_SIZE + sizeof(uint32_t)], &sha256.loLen, sizeof(uint32_t)); + + XTRANSFORM(&sha256, (const uint8_t*)local); + + ByteReverseWords((uint32_t* )tmpHash, sha256.digest, NERD_DIGEST_SIZE); + + hash = tmpHash; + + //*********** end SHA_finish *********** + + // ----- 2nd SHA ------------ + //Init SHA context again + XMEMSET(sha256.digest, 0, sizeof(sha256.digest)); + sha256.digest[0] = 0x6A09E667L; + sha256.digest[1] = 0xBB67AE85L; + sha256.digest[2] = 0x3C6EF372L; + sha256.digest[3] = 0xA54FF53AL; + sha256.digest[4] = 0x510E527FL; + sha256.digest[5] = 0x9B05688CL; + sha256.digest[6] = 0x1F83D9ABL; + sha256.digest[7] = 0x5BE0CD19L; + + sha256.buffLen = 0; + sha256.loLen = 0; + sha256.hiLen = 0; + //endINIT Sha context + + //*********** ShaUpdate *********** + len = 32; //Current hash size to make the 2nd sha256 + tmp = sha256.loLen; + if ((sha256.loLen += len) < tmp) { + sha256.hiLen++; + } + + local2 = (uint8_t*)sha256.buffer; + + // process any remainder from previous operation + if (sha256.buffLen > 0) { + blocksLen = min(len, NERD_BLOCK_SIZE - sha256.buffLen); + XMEMCPY(&local2[sha256.buffLen], hash, blocksLen); + + sha256.buffLen += blocksLen; + hash += blocksLen; + len -= blocksLen; + + if (sha256.buffLen == NERD_BLOCK_SIZE) { + + ByteReverseWords(sha256.buffer, sha256.buffer, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(&sha256, (const uint8_t*)local2); + + if (ret == 0) + sha256.buffLen = 0; + else + len = 0; // error + } + } + + // process blocks + while (len >= NERD_BLOCK_SIZE) { + uint32_t* local32 = sha256.buffer; + XMEMCPY(local32, hash, NERD_BLOCK_SIZE); + + hash += NERD_BLOCK_SIZE; + len -= NERD_BLOCK_SIZE; + + ByteReverseWords(local32, local32, NERD_BLOCK_SIZE); + + ret = XTRANSFORM(&sha256, (const uint8_t*)local32); + + if (ret != 0) + break; + } + // save remainder + if (ret == 0 && len > 0) { + XMEMCPY(local2, hash, len); + sha256.buffLen = len; + } + //*********** end update *********** + + //*********** Init SHA_finish *********** + + //local2 = (uint8_t*)sha256.buffer; + local2[sha256.buffLen++] = 0x80; // add 1 + //local2[33] = 0x80; // add 1 + + //Padd with zeros + + if (sha256.buffLen > NERD_PAD_SIZE) { + + XMEMSET(&local2[sha256.buffLen], 0, NERD_BLOCK_SIZE - sha256.buffLen); + sha256.buffLen += NERD_BLOCK_SIZE - sha256.buffLen; + + //ByteReverseWords(sha256_2.buffer, sha256_2.buffer, NERD_BLOCK_SIZE); + XTRANSFORM(&sha256, (const uint8_t*)local2); + + sha256.buffLen = 0; + } + + XMEMSET(&local2[sha256.buffLen], 0, NERD_PAD_SIZE - sha256.buffLen); + + // put lengths in bits + sha256.hiLen = (sha256.loLen >> (8 * sizeof(sha256.loLen) - 3)) + (sha256.hiLen << 3); + sha256.loLen = sha256.loLen << 3; + + ByteReverseWords(sha256.buffer, sha256.buffer, NERD_BLOCK_SIZE); + + // ! length ordering dependent on digest endian type ! + XMEMCPY(&local2[NERD_PAD_SIZE], &sha256.hiLen, sizeof(uint32_t)); + XMEMCPY(&local2[NERD_PAD_SIZE + sizeof(uint32_t)], &sha256.loLen, sizeof(uint32_t)); + + XTRANSFORM(&sha256, (const uint8_t*)local2); + + ByteReverseWords((uint32_t*)doubleHash, sha256.digest, NERD_DIGEST_SIZE); + + return 0; +} + diff --git a/test/TestHashPerformance/src/nerdSHA256.h b/test/TestHashPerformance/src/nerdSHA256.h new file mode 100644 index 00000000..c4b866ae --- /dev/null +++ b/test/TestHashPerformance/src/nerdSHA256.h @@ -0,0 +1,26 @@ +#ifndef nerdSHA256_H_ +#define nerdSHA256_H_ + +#include +#include +#include + +#define NERD_DIGEST_SIZE 32 +#define NERD_BLOCK_SIZE 64 +#define NERD_PAD_SIZE 56 + +struct nerd_sha256 { + uint32_t digest[NERD_DIGEST_SIZE / sizeof(uint32_t)]; + uint32_t buffer[NERD_BLOCK_SIZE / sizeof(uint32_t)]; + uint32_t buffLen; /* in bytes */ + uint32_t loLen; /* length in bytes */ + uint32_t hiLen; /* length in bytes */ + void* heap; +}; + +/* Calculate midstate */ +IRAM_ATTR int nerd_midstate(nerd_sha256* sha256, uint8_t* data, uint32_t len); + +IRAM_ATTR int nerd_double_sha(nerd_sha256* midstate, uint8_t* data, uint8_t* doubleHash); + +#endif /* nerdSHA256_H_ */ \ No newline at end of file diff --git a/test/TestHashPerformance/src/testShaPerformance.cpp b/test/TestHashPerformance/src/testShaPerformance.cpp index b8e4fdc2..65e0fda6 100644 --- a/test/TestHashPerformance/src/testShaPerformance.cpp +++ b/test/TestHashPerformance/src/testShaPerformance.cpp @@ -5,6 +5,7 @@ #include "jadeSHA256.h" #include "customSHA256.h" +#include "nerdSHA256.h" #include "mbedtls/md.h" #include "mbedtls/sha256.h" #include @@ -65,15 +66,20 @@ void loop() { Serial.println(""); //Test WOLF - Sha256 midstate[32]; + Sha256 midstate; Sha256 sha256; uint8_t hash2[32]; - wc_InitSha256(midstate); - wc_Sha256Update(midstate, blockheader, 64); + wc_InitSha256(&midstate); + wc_Sha256Update(&midstate, blockheader, 64); + Serial.print("Wolf midstate: "); + for (size_t i = 0; i < 8; i++) + Serial.printf("%02x", midstate.digest[i]); + Serial.println(""); + // Mining starts here //Primer sha startT = micros(); - wc_Sha256Copy(midstate, &sha256); + wc_Sha256Copy(&midstate, &sha256); wc_Sha256Update(&sha256, blockheader+64, 16); wc_Sha256Final(&sha256, hash2); // Segundo SHA-256 @@ -94,6 +100,13 @@ void loop() { mbedtls_sha256_init(&midstate3); mbedtls_sha256_starts_ret(&midstate3, 0); mbedtls_sha256_update_ret(&midstate3, blockheader, 64); + Serial.println("Mbed midstate:"); + for (size_t i = 0; i < 8; i++) + Serial.printf("%02x", midstate3.state[i]); + Serial.println(""); + for (size_t i = 0; i < 32; i++) + Serial.printf("%02x", midstate3.buffer[i]); + Serial.println(""); // Mining starts here // Primer SHA-256 @@ -115,7 +128,15 @@ void loop() { //Test Jade SHA _sha256_context midstate_cached = { 0 }; + memcpy(midstate_cached.buffer, blockheader, 64); calc_midstate(blockheader, &midstate_cached); + Serial.println("Jade midstate:"); + for (size_t i = 0; i < 8; i++) + Serial.printf("%02x", midstate_cached.state[i]); + Serial.println(""); + for (size_t i = 0; i < 32; i++) + Serial.printf("%02x", midstate_cached.buffer[i]); + Serial.println(""); *((uint32_t*)&midstate_cached.buffer[12]) = 0xFFFFFFFF;//nonce; // Mining starts here startT = micros(); @@ -125,5 +146,23 @@ void loop() { for (size_t i = 0; i < 32; i++) Serial.printf("%02x", midstate_cached.buffer[i]); Serial.println(""); + + //Test nerdSHA + nerd_sha256 nerdMidstate; + uint8_t nerdHash[32]; + nerd_midstate(&nerdMidstate, blockheader, 64); + Serial.print("Nerd midstate: "); + for (size_t i = 0; i < 8; i++) + Serial.printf("%02x", nerdMidstate.digest[i]); + Serial.println(""); + + //Mining starts here + startT = micros(); + nerd_double_sha(&nerdMidstate, blockheader+64,nerdHash); + expired = micros() - startT; + Serial.println("Nerd double SHA[" + String(expired) + "us]:"); + for (size_t i = 0; i < 32; i++) + Serial.printf("%02x", nerdHash[i]); + Serial.println(""); -} \ No newline at end of file +} \ No newline at end of file