Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ src/test/fuzz/fuzz
src/test/test_factorn
src/bench/bench_factorn
src/qt/test/test_bitcoin-qt
src/contrib/testgen/gen_asert_test_vectors
contrib/bench/bench_factor_rho
contrib/bench/bench_factor_popen

# autoreconf
Makefile.in
Expand Down
6 changes: 3 additions & 3 deletions configure.ac
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
AC_PREREQ([2.69])
define(_CLIENT_VERSION_MAJOR, 5)
define(_CLIENT_VERSION_MINOR, 3)
define(_CLIENT_VERSION_BUILD, 420)
define(_CLIENT_VERSION_MAJOR, 6)
define(_CLIENT_VERSION_MINOR, 0)
define(_CLIENT_VERSION_BUILD, 42)
define(_CLIENT_VERSION_RC, 0)
define(_CLIENT_VERSION_IS_RELEASE, true )
define(_COPYRIGHT_YEAR, 2026)
Expand Down
167 changes: 167 additions & 0 deletions contrib/bench/bench_factor_popen.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
/*
* bench_factor_popen.c
*
* Benchmarks block solving using coreutils `factor` via popen (batch).
* Simulates solving NBLOCKS blocks at nBits=32 (regtest difficulty).
*
* Build: gcc -O2 -o bench_factor_popen bench_factor_popen.c
* Usage: poop ./bench_factor_rho ./bench_factor_popen
*/

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NBITS 32
#define NBLOCKS 200
#define TILDEN (16 * NBITS) /* candidate search radius */

/* Maximum candidates per W: 2 per jj step (+ and - direction) */
#define MAX_CANDIDATES (TILDEN * 2)

/* Small-prime sieve: check divisibility by small primes individually.
* We can't use a single product here since we don't have GMP. */
static const uint64_t SMALL_P[] = {3,5,7,11,13,17,19,23,29,31,37,41,43,47};
#define NSMALL (sizeof(SMALL_P) / sizeof(SMALL_P[0]))

static int coprime_to_small(uint64_t n)
{
for (int i = 0; i < (int)NSMALL; i++) {
if (n % SMALL_P[i] == 0)
return 0;
}
return 1;
}

/*
* Factor candidates for a single W using one batched `factor` call.
* Returns 1 if a valid semiprime was found.
*/
static int solve_one(uint64_t W, uint64_t *out_factor, int64_t *out_offset)
{
uint64_t one = (W & 1) ? 0 : 1;

/* Collect sieve-surviving candidates with their offsets */
uint64_t cands[MAX_CANDIDATES];
int64_t offsets[MAX_CANDIDATES];
int ncands = 0;

for (int jj = 0; jj < TILDEN; jj += 2) {
uint64_t N1 = W + jj + one;
uint64_t N2 = W - jj - one;

if (coprime_to_small(N1)) {
cands[ncands] = N1;
offsets[ncands] = jj + (int64_t)one;
ncands++;
}
if (coprime_to_small(N2)) {
cands[ncands] = N2;
offsets[ncands] = -jj - (int64_t)one;
ncands++;
}
}

if (ncands == 0)
return 0;

/* Build the command: "factor N1 N2 N3 ..." */
/* Each number is at most 10 digits + space, plus "factor " prefix */
size_t cmd_cap = 8 + (size_t)ncands * 12;
char *cmd = malloc(cmd_cap);
if (!cmd) return 0;

int pos = snprintf(cmd, cmd_cap, "factor");
for (int i = 0; i < ncands; i++) {
pos += snprintf(cmd + pos, cmd_cap - pos, " %lu", (unsigned long)cands[i]);
}

FILE *fp = popen(cmd, "r");
free(cmd);
if (!fp) return 0;

/* Parse output line by line: "N: f1 f2 f3 ..." */
char line[1024];
int found = 0;

while (fgets(line, sizeof(line), fp) && !found) {
/* Parse "N: f1 f2 ..." */
char *colon = strchr(line, ':');
if (!colon) continue;

uint64_t N = strtoull(line, NULL, 10);
char *rest = colon + 1;

/* Collect distinct prime factors and their total count */
uint64_t factors[64];
int nfactors = 0;
int total_factors = 0;
char *tok = strtok(rest, " \t\n");
while (tok && nfactors < 64) {
uint64_t f = strtoull(tok, NULL, 10);
total_factors++;
/* Only add distinct factors */
if (nfactors == 0 || factors[nfactors - 1] != f) {
factors[nfactors++] = f;
}
tok = strtok(NULL, " \t\n");
}

/* A semiprime is p*q (exactly 2 prime factors total, both distinct) */
if (total_factors != 2 || nfactors != 2) continue;

/* factors[] is already sorted (factor outputs in ascending order) */

/* Smaller factor must have exactly expected bits (matches consensus) */
int b0 = 64 - __builtin_clzll(factors[0]);
int expected = NBITS / 2 + (NBITS & 1);

if (b0 != expected) continue;

/* Edge case: factor must not be a power of 2 */
if (factors[0] == (1ULL << (NBITS/2 - 1))) continue;

/* Find matching offset */
for (int i = 0; i < ncands; i++) {
if (cands[i] == N) {
*out_factor = factors[0];
*out_offset = offsets[i];
found = 1;
break;
}
}
}

pclose(fp);
return found;
}

int main(void)
{
/* Same PRNG and seed as rho benchmark for identical W values */
uint64_t state = 0xDEADBEEFCAFEBABEULL;
int solved = 0;

for (int i = 0; i < NBLOCKS; i++) {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;

uint64_t W = state;
W |= (1ULL << (NBITS - 1));
W &= (1ULL << NBITS) - 1;
W |= 1;

if (i < 5)
printf("W[%d] = %lu\n", i, (unsigned long)W);

uint64_t factor;
int64_t offset;
if (solve_one(W, &factor, &offset))
solved++;
}

printf("popen: solved %d/%d blocks\n", solved, NBLOCKS);
return 0;
}
150 changes: 150 additions & 0 deletions contrib/bench/bench_factor_rho.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
/*
* bench_factor_rho.c
*
* Benchmarks block solving using in-process Pollard's rho (GMP).
* Simulates solving NBLOCKS blocks at nBits=32 (regtest difficulty).
*
* Build: gcc -O2 -o bench_factor_rho bench_factor_rho.c -lgmp
* Usage: poop ./bench_factor_rho ./bench_factor_popen
*/

#include <gmp.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>

#define NBITS 32
#define NBLOCKS 200
#define TILDEN (16 * NBITS) /* candidate search radius */

/* Small-prime sieve product: 3*5*7*11*13*17*19*23*29*31*37*41*43*47 */
static const uint64_t SMALL_PRIMES = 3ULL*5*7*11*13*17*19*23*29*31*37*41*43*47;

/* f(z) = z^2 + 1 mod n (Pollard rho step) */
static void f(mpz_t z, const mpz_t n, const mpz_t two)
{
mpz_powm(z, z, two, n);
mpz_add_ui(z, z, 1ULL);
mpz_mod(z, z, n);
}

/*
* Pollard rho factoring.
* Returns 1 if n = g * (n/g) with both factors prime.
* Returns 0 if n is prime or factoring failed.
*/
static int rho(mpz_t g, mpz_t n)
{
if (mpz_probab_prime_p(n, 25) != 0)
return 0;

mpz_t x, y, two, temp;
mpz_init_set_ui(x, 2);
mpz_init_set_ui(y, 2);
mpz_init_set_ui(two, 2);
mpz_set_ui(g, 1);
mpz_init(temp);

while (mpz_cmp_ui(g, 1) == 0) {
f(x, n, two);
f(y, n, two);
f(y, n, two);

mpz_sub(temp, x, y);
mpz_gcd(g, temp, n);
}

mpz_divexact(temp, n, g);

int u_p = mpz_probab_prime_p(temp, 30);
int g_p = mpz_probab_prime_p(g, 30);

mpz_clear(x);
mpz_clear(y);
mpz_clear(temp);
mpz_clear(two);

if ((u_p != 0) && (g_p != 0) && (mpz_cmp(g, n) != 0))
return 1;

return 0;
}

/*
* Try to solve one "block" given W (a random nBits-bit number).
* Returns 1 if a valid semiprime factorization was found, 0 otherwise.
*/
static int solve_one(uint64_t W, uint64_t *out_factor, int64_t *out_offset)
{
mpz_t n, g, gcd_val;
mpz_inits(n, g, gcd_val, NULL);

uint64_t one = (W & 1) ? 0 : 1;
int found = 0;

for (int jj = 0; jj < TILDEN && !found; jj += 2) {
uint64_t candidates[2] = { W + jj + one, W - jj - one };
int64_t offsets[2] = { jj + (int64_t)one, -jj - (int64_t)one };

for (int k = 0; k < 2 && !found; k++) {
uint64_t N = candidates[k];
mpz_set_ui(n, N);
mpz_gcd_ui(gcd_val, n, SMALL_PRIMES);

if (mpz_cmp_ui(gcd_val, 1) != 0)
continue;

int valid = rho(g, n);
int bitsize = (int)mpz_sizeinbase(g, 2);

if (valid == 1 && bitsize == (NBITS >> 1)) {
uint64_t f1 = mpz_get_ui(g);
uint64_t f2 = N / f1;
uint64_t factor = (f1 < f2) ? f1 : f2;

/* Edge case: cofactor must have same number of bits */
int edge_check = f2 & (1ULL << (bitsize - 1));
if (edge_check != 0 && factor != (1ULL << (NBITS/2 - 1))) {
*out_factor = factor;
*out_offset = offsets[k];
found = 1;
}
}
}
}

mpz_clears(n, g, gcd_val, NULL);
return found;
}

int main(void)
{
/* Seed a simple PRNG (xorshift64) for reproducible W values */
uint64_t state = 0xDEADBEEFCAFEBABEULL;
int solved = 0;

for (int i = 0; i < NBLOCKS; i++) {
/* Generate a random nBits-bit W */
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;

uint64_t W = state;
/* Force exactly NBITS bits: set bit 31, clear bits above */
W |= (1ULL << (NBITS - 1));
W &= (1ULL << NBITS) - 1;
/* Make sure it's odd (most semiprimes are) */
W |= 1;

if (i < 5)
printf("W[%d] = %lu\n", i, (unsigned long)W);

uint64_t factor;
int64_t offset;
if (solve_one(W, &factor, &offset))
solved++;
}

printf("rho: solved %d/%d blocks\n", solved, NBLOCKS);
return 0;
}
Loading