Last active
April 22, 2025 21:14
-
-
Save compilade/9b16624b81f93fece2637347c27d0bd3 to your computer and use it in GitHub Desktop.
Fractional difficulty proof of concept, related to https://github.com/TecharoHQ/anubis/issues/106
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <math.h> | |
#include <stdbool.h> | |
#include <stdlib.h> | |
#include <stdint.h> | |
#include <stdio.h> | |
#include <string.h> | |
#include <sys/random.h> | |
void print_hex(FILE * stream, uint8_t * hash, int size) { | |
for (int i = 0; i < size; ++i) { | |
uint8_t b = hash[i]; | |
fprintf(stream, "%02x", b); | |
} | |
} | |
// Puts the max hash value (inclusive) in buf | |
void max_hash_f64(double difficulty, uint8_t * buf, int size) { | |
const double max_difficulty = size * 8; | |
double bits = max_difficulty - difficulty; | |
double max_hash = pow(2, bits); | |
// extract the exponent and mantissa | |
uint64_t mantissa = 0; | |
memcpy(&mantissa, &max_hash, sizeof(double)); | |
// equivalent of what frexp would give, the exponent of a value in range [0.5, 1) | |
// see also https://float.exposed/0x3fe0000000000000 | |
int exponent = (int)((mantissa >> 52) & 0x7FF) - 0x3FE; | |
mantissa = (mantissa << 11) | ((uint64_t) 1 << 63); | |
// round exponent to next multiple of 8 | |
{ | |
const int new_exponent = ((exponent + 7) / 8) * 8; | |
mantissa = mantissa >> (new_exponent - exponent); | |
exponent = new_exponent; | |
} | |
// cap exponent | |
if (exponent > size * 8) { | |
mantissa <<= ((size * 8) - exponent); | |
exponent = size * 8; | |
} | |
mantissa -= 1; | |
for (int i = 0; i < size; ++i) { | |
int cur_exponent = (size - i) * 8; | |
if (cur_exponent > exponent || mantissa == 0) { | |
buf[i] = 0; | |
} else { | |
buf[i] = mantissa >> 56; | |
mantissa = (mantissa << 8) + 0xFF; | |
exponent -= 8; | |
} | |
} | |
} | |
bool validate(double difficulty, uint8_t * hash, int size) { | |
uint8_t max_hash[size]; | |
max_hash_f64(difficulty, max_hash, size); | |
return memcmp(hash, max_hash, size) <= 0; | |
} | |
float test(double difficulty, int64_t iters, uint8_t * buf, int size, float * err) { | |
// using Welford's algorithm for the variance | |
int64_t successes = 0; | |
double mean = 0.0; | |
double M2 = 0.0; | |
int64_t current_attempts = 0; | |
for (int i = 0; i < iters; ++i) { | |
getrandom(buf, size, 0); | |
current_attempts += 1; | |
if (validate(difficulty, buf, size)) { | |
successes += 1; | |
double delta = current_attempts - mean; | |
mean += delta / successes; | |
double delta2 = current_attempts - mean; | |
M2 += delta * delta2; | |
current_attempts = 0; | |
} | |
} | |
// TODO: use more appropriate error margin calculation | |
if (successes > 1) { | |
*err = 2 * sqrt(M2 / ((successes - 1) * successes)); | |
} else { | |
*err = INFINITY; | |
} | |
return successes > 0 ? mean : NAN; | |
} | |
// in bytes | |
#define HASH_SIZE 32 | |
int main(int argc, char * argv[]) { | |
double difficulty = 3.0f; | |
int64_t iters = 0; | |
if (argc > 1) { | |
difficulty = strtod(argv[1], NULL); | |
} | |
if (argc > 2) { | |
iters = strtol(argv[2], NULL, 10); | |
} | |
uint8_t buf[HASH_SIZE] = {0}; | |
max_hash_f64(difficulty, buf, HASH_SIZE); | |
printf("Max hash (difficulty %7.4f): ", difficulty); | |
print_hex(stdout, buf, HASH_SIZE); | |
printf("\n"); | |
if (iters > 0) { | |
float err = 0.0f; | |
float avg_iters = test(difficulty, iters, buf, HASH_SIZE, &err); | |
printf("Difficulty of %7.4f needs %.2f +- %.2f iterations per success\n", difficulty, avg_iters, err); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Compiling with
gcc -lm -o fractional-difficulty fractional-difficulty.c
, and testing with a bunch of different values:And when testing the match rate with random bytes: