commit 51d1469cd1990f5e229e4f5ddc746971a730b2c6 Author: Luke Dashjr Date: Tue Sep 4 04:24:52 2012 +0000 Initial import diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ba1ca0e --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +*~ +*.so +*.o +a.* +todo* +example diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..77b978c --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +LIBNAME := blkmaker + +CFLAGS := -ggdb -O0 -Wall -Werror + +all: lib$(LIBNAME).so lib$(LIBNAME)_jansson.so + +example: example.o lib$(LIBNAME).so lib$(LIBNAME)_jansson.so + $(CC) $(LDFLAGS) -o $@ $^ -ljansson -lgcrypt -Wl,-rpath,. + +lib$(LIBNAME).so: blkmaker.o blktemplate.o + +lib$(LIBNAME)_jansson.so: blkmaker_jansson.o + +%.so: + $(CC) -shared $(LDFLAGS) -o $@ $^ + +%.o: %.c *.h + $(CC) -c -fPIC -I. $(CFLAGS) -std=c99 -o $@ $< + +clean: + rm -f *.so *.o diff --git a/README b/README new file mode 100644 index 0000000..d5820a2 --- /dev/null +++ b/README @@ -0,0 +1,16 @@ +Dependencies: + Jansson 2.0 with 'long long' support + +Example dependencies: + Jansson 2.1 (to read JSON from stdin) + libgcrypt (for SHA256) + +For usage, check out example.c +Note that you must assign blkmk_sha256_impl to a function pointer: + bool mysha256(void *hash_out, const void *data, size_t datasz) +hash_out must be able to overlap with data! + +Also note that you should NOT roll ntime for data retrieved; while it will +probably work, there is no guarantee it won't fall outside the maxtime limits +for the blocktemplate. It is usually best to simply get more data as often +as it is needed. diff --git a/blkmaker.c b/blkmaker.c new file mode 100644 index 0000000..f4710cd --- /dev/null +++ b/blkmaker.c @@ -0,0 +1,96 @@ +#include +#include +#include +#include +#include + +#include +#include + +static inline +void my_htole32(unsigned char *buf, uint32_t n) { + buf[0] = (n >> 0) % 256; + buf[1] = (n >> 8) % 256; + buf[2] = (n >> 16) % 256; + buf[3] = (n >> 24) % 256; +} + + +bool (*blkmk_sha256_impl)(void *, const void *, size_t) = NULL; + +static +bool dblsha256(void *hash, const void *data, size_t datasz) { + return blkmk_sha256_impl(hash, data, datasz) && blkmk_sha256_impl(hash, hash, 32); +} + +static +bool build_merkle_root(unsigned char *mrklroot_out, blktemplate_t *tmpl) { + if (!tmpl->cbtxn) + return false; + + size_t hashcount = tmpl->txncount + 1; + unsigned char hashes[(hashcount + 1) * 32]; + + if (!dblsha256(&hashes[0], tmpl->cbtxn->data, tmpl->cbtxn->datasz)) + return false; + for (int i = 0; i < tmpl->txncount; ++i) + if (!dblsha256(&hashes[32 * (i + 1)], tmpl->txns[i].data, tmpl->txns[i].datasz)) + return false; + + while (hashcount > 1) + { + if (hashcount % 2) + { + memcpy(&hashes[32 * hashcount], &hashes[32 * (hashcount - 1)], 32); + ++hashcount; + } + for (int i = 0; i < hashcount; i += 2) + // This is where we overlap input and output, on the first pair + if (!dblsha256(&hashes[i / 2 * 32], &hashes[32 * i], 64)) + return false; + hashcount /= 2; + } + + memcpy(mrklroot_out, &hashes[0], 32); + + return true; +} + +size_t blkmk_get_data(blktemplate_t *tmpl, void *buf, size_t bufsz, time_t usetime, int16_t *out_expire) { + if (!(blkmk_time_left(tmpl, usetime) && blkmk_work_left(tmpl))) + return 0; + if (bufsz < 76) + return 76; + + unsigned char *cbuf = buf; + + my_htole32(&cbuf[0], tmpl->version); + memcpy(&cbuf[4], &tmpl->prevblk, 32); + if (!build_merkle_root(&cbuf[36], tmpl)) + return 0; + blktime_t timehdr = tmpl->curtime + difftime(usetime, tmpl->_time_rcvd); + if (timehdr > tmpl->maxtime) + timehdr = tmpl->maxtime; + my_htole32(&cbuf[68], timehdr); + memcpy(&cbuf[72], &tmpl->diffbits, 4); + // TODO: set *out_expire if provided + + // TEMPORARY HACK: + memcpy(tmpl->_mrklroot, &cbuf[36], 32); + + return 76; +} + +blktime_diff_t blkmk_time_left(const blktemplate_t *tmpl, time_t nowtime) { + double age = difftime(nowtime, tmpl->_time_rcvd); + if (age >= tmpl->expires) + return 0; + return tmpl->expires - age; +} + +unsigned long blkmk_work_left(const blktemplate_t *tmpl) { + // TODO + // TEMPORARY HACK + return (tmpl->version && !tmpl->_mrklroot[0]) ? 1 : 0; + return BLKMK_UNLIMITED_WORK_COUNT; +} diff --git a/blkmaker.h b/blkmaker.h new file mode 100644 index 0000000..22ac857 --- /dev/null +++ b/blkmaker.h @@ -0,0 +1,17 @@ +#ifndef BLKMAKER_H +#define BLKMAKER_H + +#include + +#include + +#define BLKMAKER_VERSION (0L) + +extern bool (*blkmk_sha256_impl)(void *hash_out, const void *data, size_t datasz); + +extern size_t blkmk_get_data(blktemplate_t *, void *buf, size_t bufsz, time_t usetime, int16_t *out_expire); +extern blktime_diff_t blkmk_time_left(const blktemplate_t *, time_t nowtime); +extern unsigned long blkmk_work_left(const blktemplate_t *); +#define BLKMK_UNLIMITED_WORK_COUNT ULONG_MAX + +#endif diff --git a/blkmaker_jansson.c b/blkmaker_jansson.c new file mode 100644 index 0000000..06c97b3 --- /dev/null +++ b/blkmaker_jansson.c @@ -0,0 +1,302 @@ +#define _BSD_SOURCE + +#include +#include + +#include + +#include + +#include + +#ifndef JSON_INTEGER_IS_LONG_LONG +# error "Jansson 2.0 with long long support required!" +#endif + +json_t *blktmpl_request_jansson(gbt_capabilities_t caps) { + json_t *req, *jcaps, *jstr, *reqf, *reqa; + if (!(req = json_object())) + return NULL; + jstr = reqa = jcaps = NULL; + if (!(reqf = json_object())) + goto err; + if (!(reqa = json_array())) + goto err; + if (!(jcaps = json_array())) + goto err; + for (int i = 0; i < GBT_CAPABILITY_COUNT; ++i) + if (caps & (1 << i)) + { + jstr = json_string(blktmpl_capabilityname(1 << i)); + if (!jstr) + goto err; + if (json_array_append_new(jcaps, jstr)) + goto err; + } + if (!(jstr = json_integer(0))) + goto err; + if (json_object_set_new(req, "capabilities", jcaps)) + goto err; + if (json_object_set_new(reqf, "id", jstr)) + goto err; + if (!(jstr = json_string("getblocktemplate"))) + goto err; + if (json_object_set_new(reqf, "method", jstr)) + goto err; + if (json_array_append_new(reqa, req)) + goto err; + if (json_object_set_new(reqf, "params", reqa)) + goto err; + + return reqf; + +err: + if (req ) json_decref(req ); + if (reqa ) json_decref(reqa ); + if (reqf ) json_decref(reqf ); + if (jcaps) json_decref(jcaps); + if (jstr ) json_decref(jstr ); + return NULL; +} + + +static bool my_hex2bin(void *o, const char *x, size_t len) { + unsigned char *oc = o; + unsigned char c, hc = 0x10; + len *= 2; + while (len) + { + switch (x[0]) { + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + c = x[0] - '0'; + break; + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': + c = x[0] - 'A' + 10; + break; + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': + c = x[0] - 'a' + 10; + break; + default: + return false; + } + ++x; + if (hc < 0x10) + { + (oc++)[0] = (hc << 4) | c; + hc = 0x10; + } + else + hc = c; + --len; + } + return !x[0]; +} + +#define GET(key, type) do { \ + if (!(v = json_object_get(json, #key))) \ + return "Missing '" #key "'"; \ + if (!json_is_ ## type(v)) \ + return "Wrong type for '" #key "'"; \ +} while(0) + +#define GETHEX(key, skey) do { \ + GET(key, string); \ + if (!my_hex2bin(tmpl->skey, json_string_value(v), sizeof(tmpl->skey))) \ + return "Error decoding '" #key "'"; \ +} while(0) + +#define GETNUM(key) do { \ + GET(key, number); \ + tmpl->key = json_integer_value(v); \ +} while(0) + +static +const char *parse_txn(struct blktxn_t *txn, json_t *txnj) { + json_t *vv; + + if (!((vv = json_object_get(txnj, "data")) && json_is_string(vv))) + return "Missing or invalid type for transaction data"; + const char *hexdata = json_string_value(vv); + size_t datasz = strlen(hexdata) / 2; + txn->data = malloc(datasz); + txn->datasz = datasz; + if (!my_hex2bin(txn->data, hexdata, datasz)) + return "Error decoding transaction data"; + + if ((vv = json_object_get(txnj, "hash")) && json_is_string(vv)) + { + hexdata = json_string_value(vv); + txn->hash = malloc(sizeof(*txn->hash)); + if (!my_hex2bin(*txn->hash, hexdata, sizeof(*txn->hash))) + { + free(txn->hash); + txn->hash = NULL; + } + } + + // TODO: dependcount/depends, fee, required, sigops + + return NULL; +} + +static +void my_flip(void *data, size_t datasz) { + char *cdata = (char*)data; + --datasz; + size_t hds = datasz / 2; + for (int i = 0; i <= hds; ++i) + { + int altp = datasz - i; + char c = cdata[i]; + cdata[i] = cdata[altp]; + cdata[altp] = c; + } +} + +const char *blktmpl_add_jansson(blktemplate_t *tmpl, json_t *json, time_t time_rcvd) { + if (tmpl->version) + return false; + + json_t *v; + const char *s; + + if ((v = json_object_get(json, "result"))) + { + json_t *je; + if ((je = json_object_get(json, "error")) && !json_is_null(je)) + return "JSON result is error"; + json = v; + } + + GETHEX(bits, diffbits); + GETNUM(curtime); + GETNUM(height); + GETHEX(previousblockhash, prevblk); + my_flip(tmpl->prevblk, 32); + GETNUM(sigoplimit); + GETNUM(sizelimit); + GETNUM(version); + + if ((v = json_object_get(json, "coinbasevalue")) && json_is_number(v)) + tmpl->cbvalue = json_integer_value(v); + + if ((v = json_object_get(json, "workid")) && json_is_string(v)) + if (!(tmpl->workid = strdup(json_string_value(v)))) + return "Error copying 'workid'"; + + v = json_object_get(json, "transactions"); + size_t txns = tmpl->txncount = json_array_size(v); + tmpl->txns = calloc(txns, sizeof(*tmpl->txns)); + for (size_t i = 0; i < txns; ++i) + if ((s = parse_txn(&tmpl->txns[i], json_array_get(v, i)))) + return s; + + if ((v = json_object_get(json, "coinbasetxn")) && json_is_object(v)) + { + tmpl->cbtxn = calloc(1, sizeof(*tmpl->cbtxn)); + if ((s = parse_txn(tmpl->cbtxn, v))) + return s; + } + + // TODO: coinbaseaux + + tmpl->_time_rcvd = time_rcvd; + + return NULL; +} + +static +char varintEncode(unsigned char *out, uint64_t n) { + if (n < 0xfd) + { + out[0] = n; + return 1; + } + char L; + if (n <= 0xffff) + { + out[0] = '\xfd'; + L = 3; + } + else + if (n <= 0xffffffff) + { + out[0] = '\xfe'; + L = 5; + } + else + { + out[0] = '\xff'; + L = 9; + } + for (unsigned char i = 1; i < L; ++i) + out[i] = (n >> ((i - 1) * 8)) % 256; + return L; +} + +json_t *blkmk_submit_jansson(blktemplate_t *tmpl, const unsigned char *data, blknonce_t nonce) { + unsigned char blk[80 + 8 + 1000000]; + memcpy(blk, data, 76); + *(uint32_t*)(&blk[76]) = htonl(nonce); + size_t offs = 80; + offs += varintEncode(&blk[offs], 1 + tmpl->txncount); + + memcpy(&blk[offs], tmpl->cbtxn->data, tmpl->cbtxn->datasz); + offs += tmpl->cbtxn->datasz; + for (int i = 0; i < tmpl->txncount; ++i) + { + memcpy(&blk[offs], tmpl->txns[i].data, tmpl->txns[i].datasz); + offs += tmpl->txns[i].datasz; + } + + char blkhex[(offs * 2) + 1]; + blkhex[offs * 2] = '\0'; + static char hex[] = "0123456789abcdef"; + for (size_t i = 0; i < offs; ++i) + { + blkhex[ i*2 ] = hex[blk[i] >> 4]; + blkhex[(i*2)+1] = hex[blk[i] & 15]; + } + + json_t *rv = json_array(), *ja, *jb; + jb = NULL; + if (!(ja = json_string(blkhex))) + goto err; + if (json_array_append_new(rv, ja)) + goto err; + if (!(ja = json_object())) + goto err; + if (tmpl->workid) + { + if (!(jb = json_string(tmpl->workid))) + goto err; + if (json_object_set_new(ja, "workid", jb)) + goto err; + jb = NULL; + } + if (json_array_append_new(rv, ja)) + goto err; + + if (!(ja = json_object())) + goto err; + if (!(jb = json_integer(0))) + goto err; + if (json_object_set_new(ja, "id", jb)) + goto err; + if (!(jb = json_string("submitblock"))) + goto err; + if (json_object_set_new(ja, "method", jb)) + goto err; + jb = NULL; + if (json_object_set_new(ja, "params", rv)) + goto err; + + return ja; + +err: + json_decref(rv); + if (ja) json_decref(ja); + if (jb) json_decref(jb); + return NULL; +} diff --git a/blkmaker_jansson.h b/blkmaker_jansson.h new file mode 100644 index 0000000..e3fe0cd --- /dev/null +++ b/blkmaker_jansson.h @@ -0,0 +1,12 @@ +#ifndef BLKMAKER_JANSSON_H +#define BLKMAKER_JANSSON_H + +#include + +#include + +extern json_t *blktmpl_request_jansson(gbt_capabilities_t extracaps); +extern const char *blktmpl_add_jansson(blktemplate_t *, json_t *, time_t time_rcvd); +extern json_t *blkmk_submit_jansson(blktemplate_t *, const unsigned char *data, blknonce_t); + +#endif diff --git a/blktemplate.c b/blktemplate.c new file mode 100644 index 0000000..2f69141 --- /dev/null +++ b/blktemplate.c @@ -0,0 +1,86 @@ +#define _BSD_SOURCE + +#include +#include +#include + +#include + +static const char *capnames[] = { + "coinbasetxn", + "coinbasevalue", + "workid", + + "longpoll", + "proposal", + "serverlist", + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + + "coinbase/append", + "coinbase", + "generate", + "time/increment", + "time/decrement", + "transactions/add", + "prevblock", + NULL, + + "submit/hash", + "submit/coinbase", + "submit/truncate", + "share/coinbase", + "share/merkle", + "share/truncate", +}; + +const char *blktmpl_capabilityname(gbt_capabilities_t caps) { + for (int i = 0; i < sizeof(capnames); ++i) + if (caps & (1 << i)) + return capnames[i]; + return NULL; +} + +blktemplate_t *blktmpl_create() { + blktemplate_t *tmpl; + tmpl = calloc(1, sizeof(*tmpl)); + + tmpl->maxtime = 0xffffffff; + tmpl->maxtimeoff = 0x7fff; + tmpl->mintimeoff = -0x7fff; + tmpl->maxnonce = 0xffffffff; + tmpl->expires = 0x7fff; + + return tmpl; +} + +gbt_capabilities_t blktmpl_addcaps(const blktemplate_t *tmpl) { + // TODO: make this a lot more flexible for merging + // For now, it's a simple "filled" vs "not filled" + if (tmpl->version) + return 0; + return GBT_CBTXN | GBT_WORKID | BMM_CBAPPEND | BMM_CBSET | BMM_TIMEINC | BMM_TIMEDEC; +} + +static +void blktxn_free(struct blktxn_t *bt) { + free(bt->data); + free(bt->hash); + free(bt->depends); +} + +void blktmpl_free(blktemplate_t *tmpl) { + for (int i = 0; i < tmpl->txncount; ++i) + blktxn_free(&tmpl->txns[i]); + free(tmpl->txns); + if (tmpl->cbtxn) + { + blktxn_free(tmpl->cbtxn); + free(tmpl->cbtxn); + } + // TODO: maybe free auxnames[0..n]? auxdata too + free(tmpl->auxnames); + free(tmpl->auxdata); + free(tmpl->workid); + free(tmpl); +} diff --git a/blktemplate.h b/blktemplate.h new file mode 100644 index 0000000..c273b12 --- /dev/null +++ b/blktemplate.h @@ -0,0 +1,117 @@ +#ifndef BLKTEMPLATE_H +#define BLKTEMPLATE_H + +#include +#include +#include + +typedef uint32_t blkheight_t; +typedef uint32_t libblkmaker_hash_t[8]; +typedef libblkmaker_hash_t blkhash_t; +typedef libblkmaker_hash_t txnhash_t; +typedef uint32_t blktime_t; +typedef int16_t blktime_diff_t; +typedef uint32_t blknonce_t; + +struct blktxn_t { + unsigned char *data; + size_t datasz; + txnhash_t *hash; + + signed long dependcount; + unsigned long *depends; + + uint64_t fee; + bool required; + int16_t sigops; +}; + +// BIP 23: Long Polling +struct blktmpl_longpoll_req { + char *id; + char *uri; +}; + + +typedef enum { + GBT_CBTXN = 1 << 0, + GBT_CBVALUE = 1 << 1, + GBT_WORKID = 1 << 2, + + GBT_LONGPOLL = 1 << 3, // BIP 22: Long Polling + GBT_PROPOSAL = 1 << 4, // BIP 23: Block Proposal + GBT_SERVICE = 1 << 5, // BIP 23: Logical Services + + // BIP 23: Mutations + BMM_CBAPPEND = 1 << 0x10, + BMM_CBSET = 1 << 0x11, + BMM_GENERATE = 1 << 0x12, + BMM_TIMEINC = 1 << 0x13, + BMM_TIMEDEC = 1 << 0x14, + BMM_TXNADD = 1 << 0x15, + BMM_PREVBLK = 1 << 0x16, + + // BIP 23: Submission Abbreviation + BMA_TXNHASH = 1 << 0x18, + BMAb_COINBASE = 1 << 0x19, + BMAb_TRUNCATE = 1 << 0x1a, + BMAs_COINBASE = 1 << 0x1b, + BMAs_MERKLE = 1 << 0x1c, + BMAs_TRUNCATE = 1 << 0x1d, +} gbt_capabilities_t; +#define GBT_CAPABILITY_COUNT (0x1e) + +extern const char *blktmpl_capabilityname(gbt_capabilities_t); +#define BLKTMPL_LONGEST_CAPABILITY_NAME (16) + + +typedef gbt_capabilities_t blkmutations_t; + +typedef struct { + uint32_t version; + unsigned char diffbits[4]; + blkheight_t height; + blkhash_t prevblk; + + unsigned short sigoplimit; + unsigned long sizelimit; + + unsigned long txncount; + struct blktxn_t *txns; + struct blktxn_t *cbtxn; + uint64_t cbvalue; + + time_t _time_rcvd; + blktime_t curtime; + char auxcount; + char **auxnames; + unsigned char **auxdata; + + char *workid; + + // BIP 22: Long Polling + struct blktmpl_longpoll_req lp; + bool submitold; + + // BIP 23: Basic Pool Extensions + int16_t expires; + blkhash_t target; + + // BIP 23: Mutations + blkmutations_t mutations; + blktime_t maxtime; + blktime_diff_t maxtimeoff; + blktime_t mintime; + blktime_diff_t mintimeoff; + blknonce_t minnonce; + blknonce_t maxnonce; + + // TEMPORARY HACK + libblkmaker_hash_t _mrklroot; +} blktemplate_t; + +extern blktemplate_t *blktmpl_create(); +extern gbt_capabilities_t blktmpl_addcaps(const blktemplate_t *); +extern void blktmpl_free(blktemplate_t *); + +#endif diff --git a/example.c b/example.c new file mode 100644 index 0000000..32fbe8a --- /dev/null +++ b/example.c @@ -0,0 +1,85 @@ +#include +#include +#include + +#include + +#include + +#include +#include + +static +void send_json(json_t *req) { + char *s = json_dumps(req, JSON_INDENT(2)); + puts(s); + free(s); +} + +static +bool my_sha256(void *digest, const void *buffer, size_t length) { + gcry_md_hash_buffer(GCRY_MD_SHA256, digest, buffer, length); + return true; +} + +int main(int argc, char**argv) { + blktemplate_t *tmpl; + json_t *req; + json_error_t jsone; + const char *err; + + blkmk_sha256_impl = my_sha256; + + tmpl = blktmpl_create(); + assert(tmpl); + req = blktmpl_request_jansson(blktmpl_addcaps(tmpl)); + assert(req); + + // send req to server and parse response into req + send_json(req); + json_decref(req); + req = json_loadf(stdin, JSON_DISABLE_EOF_CHECK, &jsone); + assert(req); + + err = blktmpl_add_jansson(tmpl, req, time(NULL)); + json_decref(req); + if (err) + { + fprintf(stderr, "Error adding block template: %s", err); + assert(0 && "Error adding block template"); + } + while (blkmk_time_left(tmpl, time(NULL)) && blkmk_work_left(tmpl)) + { + unsigned char data[80], hash[32]; + size_t datasz; + uint32_t nonce; + + datasz = blkmk_get_data(tmpl, data, sizeof(data), time(NULL), NULL); + assert(datasz <= sizeof(data)); + + // mine the right nonce + // this is iterating in native order, even though SHA256 is big endian, because we don't implement noncerange + // however, the nonce is always interpreted as big endian, so we need to convert it as if it were big endian + for (nonce = 0; nonce < 0xffffffff; ++nonce) + { + *(uint32_t*)(&data[76]) = nonce; + assert(my_sha256(hash, data, 80)); + assert(my_sha256(hash, hash, 32)); + if (!*(uint32_t*)(&hash[28])) + break; + if (!(nonce % 0x1000)) + { + printf("0x%8" PRIx32 " hashes done...\r", nonce); + fflush(stdout); + } + } + printf("Found nonce: 0x%8" PRIx32 " \n", nonce); + nonce = ntohl(nonce); + + req = blkmk_submit_jansson(tmpl, data, nonce); + assert(req); + // send req to server + send_json(req); + } + blktmpl_free(tmpl); +}