Sample code for Dworkian memory-bound POW

Anonymous <[email protected]> Mon, 24 May 2004 10:56:45 +0200 (CEST)
Newsgroups gmane.mail.spam.hashcash,gmane.mail.spam.camram
Message-ID <[email protected]>
/* mempow.c
 *
 * Memory based proof of work
 * Based on Dwork, Goldberg and Naor, Crypto 03
 *
 * Note that mempow.dat used with this comes from the 1st two
 * 10 MB files from http://www.random.org/.
 * http://www.random.org/files/10megs.001
 * http://www.random.org/files/10megs.002
 * Concatenate them and then truncate to 16778240 (2^24 + 2^10) bytes.
 * md5sum: 283e22de6826cfbdaddfc1d2c21afada  mempow.dat
 */

#include <stdio.h>
#include "openssl/sha.h"

#define DATFILE		"mempow.dat"

/* T holds 2^22 entries */
#define TLEN 		(2048*2048)
#define ALEN 		256
#define HASHLEN		16
#define PREFIX		"MEMPOW"
#define PREFIXLEN	6
#define elemsof(x)	(sizeof(x)/sizeof((x)[0]))

/* This value of L, the chain length, is tuned for >= 14 zero bits */
#define L			2048

typedef unsigned char uchar;
typedef unsigned long ulong;

ulong T[TLEN];			/* Fixed */
ulong A0[ALEN];			/* Seed for A */
ulong A[ALEN];			/* Variable */
ulong A1[ALEN];			/* Network byte order version of A */

int verboseflag = 0;

/* Compute a hash chain value */
void
chain (uchar out[HASHLEN], uchar salt[HASHLEN], int k)
{
	uchar buf[PREFIXLEN + HASHLEN + 4];
	uchar md[SHA_DIGEST_LENGTH];
	ulong mdl[HASHLEN/4];
	int i, j, l, c;
	ulong tmp;

	/* Step 1: compute A */

	strcpy (buf, PREFIX);
	memcpy (buf+PREFIXLEN, salt, HASHLEN);
	buf[PREFIXLEN+HASHLEN]   = (uchar)(k >> 24);
	buf[PREFIXLEN+HASHLEN+1] = (uchar)(k >> 16);
	buf[PREFIXLEN+HASHLEN+2] = (uchar)(k >> 8);
	buf[PREFIXLEN+HASHLEN+3] = (uchar)(k);

	SHA1 (buf, sizeof(buf), md);

	for (i=0; i<HASHLEN/4; i++)
		mdl[i] = ntohl(((ulong *)md)[i]);
	for (i=0; i<sizeof(A); i+=HASHLEN)
		memcpy (A + i/sizeof(A[0]), mdl, HASHLEN);
	for (i=0; i<elemsof(A); i++)
		A[i] ^= A0[i];

	/* Step 2: iterate l steps */

	c = A[ALEN-1];
	i = 0;
	j = 0;

	for (l=0; l<L; l++)
	{
		c &= 0x3fffff;
		i = (i+1) % ALEN;
		j = (j + A[i]) % ALEN;
		A[i] = A[i] + T[c];
		A[i] = (A[i] << 21) | (A[i] >> 11);
		tmp = A[i]; A[i] = A[j]; A[j] = tmp;
		c = T[c] ^ A[(A[i] + A[j])%ALEN];
	}

	/* Step 3: return HASHLEN bytes of hash of A */

	/* A1 is a copy of A with its bytes in network order. */
	for (i=0; i<elemsof(A); i++)
		A1[i] = htonl(A[i]);
	SHA1 ((uchar *)A1, sizeof(A1), md);
	memcpy (out, md, HASHLEN);
}

/* Count the zero low order bits in a hash output buffer */
int
countbits (uchar *buf, int buflen)
{
	int ibuflen = buflen;
	int b;
	int nbits;

	/* Count low order 0 bits of buf */
	while (buflen--)
		if (buf[buflen] != 0)
			break;
	nbits = 8*(ibuflen-buflen-1);
	b = buf[buflen];
	while ((b&1) == 0)
	{
		b >>= 1;
		++nbits;
	}
	return nbits;
}


/* Search for a value of specified number of bits */
void
gen (uchar *salt, int nbits)
{
	int k = 0;
	uchar md[HASHLEN];
	int curbits;
	int maxbits = -1;

	RAND_bytes (salt, HASHLEN);
	for ( ; ; )
	{
		chain (md, salt, k);
		curbits = countbits(md, sizeof(md));
		if (verboseflag && (curbits > maxbits))
		{
			printf ("Max bits: %2d\n", curbits);
			maxbits = curbits;
		}
		if (curbits >= nbits)
			break;
		k += 1;
	}
	salt[HASHLEN]   = (uchar) (k>>24);
	salt[HASHLEN+1] = (uchar) (k>>16);
	salt[HASHLEN+2] = (uchar) (k>>8);
	salt[HASHLEN+3] = (uchar) (k);
}

/* Return the number of bits in a claimed collision value */
int
test (uchar salt[HASHLEN+4])
{
	uchar md[HASHLEN];
	int k;
	k = (salt[HASHLEN] << 24) | (salt[HASHLEN+1] << 16)
			| (salt[HASHLEN+2] << 8) | salt[HASHLEN+3];
	chain (md, salt, k);
	return countbits(md, sizeof(md));
}


int
main (int ac, char **av)
{
	int nbits;
	FILE *frand;
	int k;
	int dogen=0, dotest=0;
	uchar salt[HASHLEN + 4];
	int i;

	if (ac != 3 && ac != 4)
	{
		fprintf (stderr, "Usage: %s [-v] -g nbits\n"
				"\t-t kval\n", av[0]);
		exit (1);
	}

	if (strcmp (av[1], "-v") == 0)
	{
		verboseflag = 1;
		av[1] = av[2]; av[2] = av[3];
	}

	if (strcmp (av[1], "-g") == 0)
		dogen = 1;
	else if (strcmp (av[1], "-t") == 0)
		dotest = 1;
	else {
		fprintf (stderr, "Illegal option\n");
		exit (1);
	}

	if (dogen)
		nbits = atoi(av[2]);

	if ((frand = fopen (DATFILE, "rb")) == NULL)
	{
		fprintf (stderr, "Unable to open random data file %s\n", DATFILE);
		fprintf (stderr, "See the source code for how to construct this file\n");
		exit (1);
	}

	if (fread (T, 1, sizeof(T), frand) != sizeof(T)
		|| fread (A0, 1, sizeof(A0), frand) != sizeof(A0))

	{
		fprintf (stderr, "Unable to read sufficient data from DATFILE\n");
		fprintf (stderr, "See the source code for how to construct this file\n");
		exit (1);
	}
	fclose (frand);

	/* Correct for byte order on little endian machines */
	for (i=0; i<elemsof(T); i++)
		T[i] = ntohl(T[i]);
	for (i=0; i<elemsof(A0); i++)
		A0[i] = ntohl(A0[i]);

	if (dogen)
	{
		gen(salt, nbits);
		for (i=0; i<sizeof(salt); i++)
			printf ("%02x", salt[i]);
		printf ("\n");
	}

	if (dotest)
	{
		for (i=0; i<sizeof(salt); i++)
		{
			char c = av[2][2*i];
			unsigned u1 = (c >= 'a') ? (c - 'a' + 10) :
				(c >= 'A') ? (c - 'A' + 10) : (c - '0');
			unsigned u2;
			c = av[2][2*i+1];
			u2 = (c >= 'a') ? (c - 'a' + 10) :
				(c >= 'A') ? (c - 'A' + 10) : (c - '0');
			salt[i] = (u1<<4) | u2;
		}
		nbits = test(salt);
		printf ("nbits = %d\n", nbits);
	}
	return 0;
}