Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/invpe/hashnet

When Lambo eXperiment ;-)
https://github.com/invpe/hashnet

bitcoin esp32 esp32-arduino experimental mining python3

Last synced: 20 days ago
JSON representation

When Lambo eXperiment ;-)

Awesome Lists containing this project

README

        

![image](https://github.com/user-attachments/assets/09623a9b-5106-4b57-99d5-3f25b2a11b05)

# #️⃣ HashNet

BTC solo mining on ESP32 powered with sun energy - an experiment with a statistical twist.

Initially, miners get their search space randomized using extranonce2.
Rather than simply looking for a match to the target, they evaluate the number of leading zeroes in the resulting block hashβ€”the more zeroes, the closer we are to the target. This proximity informs where to concentrate future mining efforts, especially focusing on nonce values around the extranonce2 that yielded the highest zero count.

## Process details

1. Miner-Server Interaction: Miners connect to a server linked to a solo mining pool. The server assigns each miner a random extranonce2 and a randomized nonce search range (nonce_start to nonce_end).

2. Mining and Distance Reporting: Each miner computes the block hash, reporting a "distance" value representing the leading zero count in the hash. Higher values indicate hashes closer to the target.

3. Tracking and Optimization: The server continuously tracks the best "distances" reported. It prioritizes search around the extranonce2 values with the best distances, directing 80% of assignments to these while randomizing nonce ranges. To escape potential local optima, the server assigns the remaining 20% of jobs to fresh, randomized extranonce2 values.

This approach efficiently allocates resources, balancing focus on promising search areas with occasional random exploration.
Most importantly, the project settles on the idea of solar powered mining 🍏

While this method does not guarantee a successful block solution, it is a fun project and leverages probabilistic exploration to maximize coverage within the limited search space. With a bit of luck, even a random approach can yield a winning block.

# 🚀 Performance

- ~50kh/sec per ESP32 with [NerdSHA256](https://github.com/BitMaker-hub/NerdMiner_v2/blob/dev/src/ShaTests/nerdSHA256.cpp)


# πŸ‘· Mining details

How is mining made, read on.

## Block construction

When the job is received from the pool on the hashnet server

```{"params":["6485163800173513","698bd98792535164b497b84f956349ff282860d6000024380000000000000000","01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3503eb3b0d000407a91b67042d8579090c","0a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672fffffffff03eb57ad13000000001976a914c987a18977a2139835193b3df2eea097f834966888ac8ccd66000000000016001451ed61d2f6aa260cc72cdf743e4e436a82c010270000000000000000266a24aa21a9ed084092cbd60dc237c93ffb09a4425b0f65019d2b1e1824a96e449f3bd4241dd800000000",["968d35b44bf24fec1cdfb70b3027184dbb37c63fcaf8367c77371212017d8580","7704790bbe6cd15b0f70a77f2e7022586a33d5e1f4551a91269a5feb65406e31","1946e86a87d7f2ae7d83c463f744808719723644d65d26df203675a997083e28","1d9429db36c018310fa2598714566dd959be8829e12dbddbb6aeb25a4f72bf9b","895d756adb0cd283f9e4f3cc31ef60ba7fe3dc7c341f7061b611d45079970c66","7392550e7198112fd2dbcd78d163be5ef3c7bda0ed0b323217fb1b9a65cb31ff","e00914185d96553748490fb6eb9f38d22e80f4b6a769f99e31eed0d5610dde0b","5534abb623d98de8d2d2853beb7af6e7d8f7c3c0442189fa6f04470668110a70","04be9e23347df6d25e37caebc3075a23af0bdecf711e45e6512ce675f16a8ef3","9c2794d2b82d95b2d3e0266fb8b4c2155c0da6e13fac8e3fbdaa11c0b38b4390","22034a792d001b96dd2df4086152a3edef80f273a3f16abfa50f8a52dde68615","e08d27d96d0f7f8d0860928a00b8a580b9b85b768d0335c6848777895452d3ea"],"20000000","1702f128","671ba907",true],"id":null,"method":"mining.notify"}```

The server adds

`nonce_start`, `nonce_end`, `extranonce2` for each of the miners, to steer their search towards the most promising space.

```
{"method":"work","nonce_start":70000000,"nonce_end":74999999,"merkle_branch":["0390059931687530d1f9d662f996c385abd5b6b23d13b722327ff67bebc1ace7","37e67a52247df00bb185e9f7fe4a1b0b8996d679ef4c0373c97480c0dbeefa1f","0128ae80a6f22fa503d94040545a839ddc7fab67e00e072b404bfa81462c4f09","45f21362336ec015cd2af8db738a7c9a851db81e23b68dc0f10d40a515e458fb","90b6182d63a7f183c99cdb0af98cff75566f71970c7ed03d0b3586f11329c587","d158d081622278ae7395b80de0f0078b861d6014d9c23722ca7c9328e768a85a","4880ec9ed60d0b1bbe74548707c2ffa93d039981d7252315f3268cb2a07dec54","edbbd6e34fe20324079b32c4a92470e699e27e0bd37a3b86bf5dc84392d1919a","0b66275368cc7b802426da452893587f9cc0f297159aa933fc094ef54252a012","6907195e4be703bc387240448906206a148ca9cf35d1a2850c12db976048b6d9","7f5ded533af3a6294e58b9d3a549cc7d200842afe9f154dbbbff1e2dce789c7f"],"extranonce2":"4d708f85f65147ed","sversion":"1.2","extranonce1":"dc591979","job_id":"6485163800173692","prevhash":"0da49f4c425398ecbfb26cc77402d971e39e7f8e0000802f0000000000000000","coinb1":"01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3503003c0d0004afd31b6704536a662b0c","coinb2":"0a636b706f6f6c112f736f6c6f2e636b706f6f6c2e6f72672fffffffff0391030a13000000001976a914c987a18977a2139835193b3df2eea097f834966888ac3c7863000000000016001451ed61d2f6aa260cc72cdf743e4e436a82c010270000000000000000266a24aa21a9edfffefea360e527f82db3f8995309fbeb4745af056c310db911db4cb05f90562100000000","version":"20000000","nbits":"1702f128","ntime":"671bd3af"}
```

Each miner takes the data received from the server, extracts the json and constructs the block header for future midstate double sha256 hashing.
Since the pool sends data in LE already, the only thing that we have to remember when building a block is to reverse the merkle root which we calculate on the miner performing a 2xSHA256 - which will return the hash in BE.

0. Calculate the target hash

We have to know the target to be able to know if we found a block, this is calcualted from the `nbits` value coming from the server.
It returns a target in BE for simple comparison at the end against a double hashed sha256 block.

```
void nbitsToBETarget(const std::string &nbits, uint8_t target[32]) {
memset(target, 0, 32);

char *endPtr;
uint32_t bits_value = strtoul(nbits.c_str(), &endPtr, 16);

if (*endPtr != '\0' || bits_value == 0) {
std::cerr << "Error: Invalid nbits value" << std::endl;
exit(EXIT_FAILURE);
}

uint32_t exp = bits_value >> EXPONENT_SHIFT;
uint32_t mant = bits_value & MANTISSA_MASK;
uint32_t bitShift = 8 * (exp - 3);
int byteIndex = 29 - (bitShift / 8);

if (byteIndex < 0) {
std::cerr << "Error: Invalid index, nBits exponent too small" << std::endl;
exit(EXIT_FAILURE);
}

target[byteIndex] = (mant >> 16) & 0xFF;
target[byteIndex + 1] = (mant >> 8) & 0xFF;
target[byteIndex + 2] = mant & 0xFF;
}
```

So for `nbits=1702f128` the target calcualted is `00000000000000000002f1280000000000000000000000000000000000000000`
This tells us that we have to find a hash by mangling extranonce2 and nonce, that needs to have at least `9 zeroes` ...

`000000000000000000`|`02f1280000000000000000000000000000000000000000`

To visualize, here are some distances reported:

Distance 2 : `000028913a86f97e46eda21b97b2a02432ddb4362009023279b7b29d41ee33e0` - common

Distance 3 : `000000913a86f97e46eda21b97b2a02432ddb4362009023279b7b29d41ee33e0` - common

Distance 4 : `000000003a86f97e46eda21b97b2a02432ddb4362009023279b7b29d41ee33e0` - rare

Distance 5 : `000000000086f97e46eda21b97b2a02432ddb4362009023279b7b29d41ee33e0` - super rare

2. Coinbase calculation - simply concatenating strings

```std::string coinbase = coinb1 + extranonce1 + extranonce2 + coinb2;```

- `coinb1` comes in from the pool
- `extranonce` comes in from the pool
- `extranonce2` is sent by the hashnet server
- `coinb2` comes from the pool

We simply combine these into one string.

2. Merkleroot calculation

```
["0390059931687530d1f9d662f996c385abd5b6b23d13b722327ff67bebc1ace7","37e67a52247df00bb185e9f7fe4a1b0b8996d679ef4c0373c97480c0dbeefa1f","0128ae80a6f22fa503d94040545a839ddc7fab67e00e072b404bfa81462c4f09","45f21362336ec015cd2af8db738a7c9a851db81e23b68dc0f10d40a515e458fb","90b6182d63a7f183c99cdb0af98cff75566f71970c7ed03d0b3586f11329c587","d158d081622278ae7395b80de0f0078b861d6014d9c23722ca7c9328e768a85a","4880ec9ed60d0b1bbe74548707c2ffa93d039981d7252315f3268cb2a07dec54","edbbd6e34fe20324079b32c4a92470e699e27e0bd37a3b86bf5dc84392d1919a","0b66275368cc7b802426da452893587f9cc0f297159aa933fc094ef54252a012","6907195e4be703bc387240448906206a148ca9cf35d1a2850c12db976048b6d9","7f5ded533af3a6294e58b9d3a549cc7d200842afe9f154dbbbff1e2dce789c7f"]
```

We take the transactions from the mining pool JSON data and calculate the merkle root -
hashing it with SHA256 afterwards.

```

std::string calculate_merkle_root_to_hex_BE(const uint8_t *coinbase_hash_bin, size_t hash_size, const std::vector &merkle_branch) {
// Convert the binary coinbase hash to hexadecimal string for processing
std::string merkle_root = std::string(reinterpret_cast(coinbase_hash_bin), hash_size);

// Iterate over the merkle branch and compute the combined hash
for (const std::string &hash : merkle_branch) {
std::string combined_hash = merkle_root + hexStringToBinary(hash);
merkle_root = double_sha256_to_bin_string(combined_hash);
}

// Return the final merkle root as a hexadecimal string
return bin_to_hex(reinterpret_cast(merkle_root.data()), merkle_root.size());
}

...

std::string coinbase = coinb1 + extranonce1 + extranonce2 + coinb2;
...
sha256_double(coinbase_bytes, str_len, final_hash);
std::string coinbase_hash_hex = bin_to_hex(final_hash, sizeof(final_hash));
std::string merkle_root = calculate_merkle_root_to_hex_BE(final_hash, 32, merkle_branch);

```

3. Merkle reversion

We have to reverse the merkleroot since we have hashed it with sha256.

```reverse_bytes(u8BlockHeaderBytes, 36, 32); // Reverse merkle root (32 bytes) after sha256```

4. Block Header

So it's time to concatenate all things we've built so far.
This is a simple thing, but the order is critical.

```std::string blockheader = version + prevhash + merkle_root + ntime + nbits + "00000000";```

The nonce `00000000` is empty, it will be updated as part of the midstate hashing later on.
So our block is now LE, and looks as these examples:

```
20000000|0da49f4c425398ecbfb26cc77402d971e39e7f8e0000802f0000000000000000|8e1b128e89e752ad85a2a53a608e4d2938d51d4a40eb3448a468d964a8b3f3bc|1702f128|671bd3af|00000000
20000000|3e9e3d07a502c810fa1613e7b6dedc1e91056c9a000053720000000000000000|de0a89e81e451399e16f78070e886a0920d3b447636498c297f9b72c16a85726|1702f128|671a6772|00000000
20000000|839200E748F39EC3E9F0FEEA587B436CED5DE4F7000093670000000000000000|93D855525A1A777A5C2C4809704BCD56EBFDB2D85A85366F06C78B2B055B8939|671A09FE|1702F128|41DB0900
20000000|839200E748F39EC3E9F0FEEA587B436CED5DE4F7000093670000000000000000|96C1E73FC2D8C93C440C54FD20E3FFEB2C8BE63D669806A09361BDD357840F8F|671A09FE|28F10217|6BC70000
|_version | | | | |_nonce
| | | |
|_prevhash |_ reversed by miner after sha256(merkle) |_ntime |
|_nbits
|_nbits |_nbits
```

## Hashing with midstate

To optimize hash calculations, we calculate an intermediate hash state (midstate) for the part of the block header that `remains constant`, so we only need to rehash the `nonce` in each iteration.

We perform an intermediate state, where we hash the first part of the blockheader that doesn't change when we iterate nonces.
Remember, the `extranonce2` is sent by the server per job, then miners iterate over `nonce` space.
This means, that only `nonce` changes, rest stays the same.

So we take `version+prevhash+merkle+ntime+nbits+000000` and perform a midstate on it

```
void sha256_init_with_intermediate_state(const uint8_t *bytearray_blockheader, size_t header_size_without_nonce, mbedtls_sha256_context *ctx) {
mbedtls_sha256_init(ctx);
mbedtls_sha256_starts_ret(ctx, 0); // 0 means SHA-256 (as opposed to SHA-224)
mbedtls_sha256_update_ret(ctx, bytearray_blockheader, header_size_without_nonce);
}
```

Then when iterating over `nonce`, we simply update the midstate with last 4 bytes (nonce) added in LE.

```

inline void sha256_double_with_intermediate_state(mbedtls_sha256_context *ctx, uint32_t nonce, uint8_t output[32]) {
uint8_t nonce_bytes[4];
nonce_bytes[0] = (nonce >> 0) & 0xFF; // Little-endian
nonce_bytes[1] = (nonce >> 8) & 0xFF;
nonce_bytes[2] = (nonce >> 16) & 0xFF;
nonce_bytes[3] = (nonce >> 24) & 0xFF;

// Copy the context to avoid modifying the original state
mbedtls_sha256_context temp_ctx;
mbedtls_sha256_init(&temp_ctx);
mbedtls_sha256_clone(&temp_ctx, ctx);
mbedtls_sha256_update_ret(&temp_ctx, nonce_bytes, sizeof(nonce_bytes));

uint8_t intermediate_hash[32];
mbedtls_sha256_finish_ret(&temp_ctx, intermediate_hash);

// Perform the second SHA-256 hash
mbedtls_sha256(intermediate_hash, sizeof(intermediate_hash), output, 0); // 0 means SHA-256 (as opposed to SHA-224)

// Clean up
mbedtls_sha256_free(&temp_ctx);
}
```

This approach avoids redundant hashing, recalculating only the `nonce` portion each time, while leaving the precomputed midstate intact.

## Compare to target

Since the hash is now in BE, and the target is in BE we simply compare them from the left to right:

```
bool checkValid(unsigned char *hash, unsigned char *target) {
for (int i = 0; i < 32; i++) { // Compare from most significant byte (byte 0) to least significant byte

if (hash[i] > target[i]) {
return false; // Hash is greater than the target, invalid
} else if (hash[i] < target[i]) {
return true; // Hash is smaller than the target, valid
}
// If hash[i] == target[i], continue comparing the next byte
}

// If all bytes are equal, it's valid
return true;
}
```