commit 29be8497fc7ce0db994a8a1e5bc7c2dd34b324cf
parent 75da4517bef7fb7fbe67151574b41e1160e45abb
Author: William Casarin <jb55@jb55.com>
Date: Fri, 15 Apr 2022 10:30:52 -0700
add proof-of-work mining
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
M | nostril.c | | | 72 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- |
A | proof.h | | | 26 | ++++++++++++++++++++++++++ |
2 files changed, 95 insertions(+), 3 deletions(-)
diff --git a/nostril.c b/nostril.c
@@ -16,6 +16,7 @@
#include "aes.h"
#include "sha256.h"
#include "random.h"
+#include "proof.h"
#define MAX_TAGS 32
#define MAX_TAG_ELEMS 16
@@ -24,6 +25,7 @@
#define HAS_KIND (1<<2)
#define HAS_ENVELOPE (1<<3)
#define HAS_ENCRYPT (1<<4)
+#define HAS_DIFFICULTY (1<<5)
struct key {
secp256k1_keypair pair;
@@ -34,6 +36,7 @@ struct key {
struct args {
unsigned int flags;
int kind;
+ int difficulty;
unsigned char encrypt_to[32];
const char *sec;
@@ -72,6 +75,7 @@ void usage()
printf(" --kind <number> set kind\n");
printf(" --created-at <unix timestamp> set a specific created-at time\n");
printf(" --sec <hex seckey> set the secret key for signing, otherwise one will be randomly generated\n");
+ printf(" --pow <difficulty> number of leading 0 bits of the id to mine\n");
exit(1);
}
@@ -375,6 +379,14 @@ static int parse_args(int argc, const char *argv[], struct args *args)
args->flags |= HAS_KIND;
} else if (!strcmp(arg, "--envelope")) {
args->flags |= HAS_ENVELOPE;
+ } else if (!strcmp(arg, "--pow")) {
+ arg = *argv++; argc--;
+ if (!parse_num(arg, &n)) {
+ fprintf(stderr, "could not parse difficulty as number: '%s'\n", arg);
+ return 0;
+ }
+ args->difficulty = n;
+ args->flags |= HAS_DIFFICULTY;
} else if (!strcmp(arg, "--dm")) {
arg = *argv++; argc--;
if (!hex_decode(arg, strlen(arg), args->encrypt_to, 32)) {
@@ -439,6 +451,53 @@ static int copyx(unsigned char *output, const unsigned char *x32, const unsigned
return 1;
}
+static int ensure_nonce_tag(struct nostr_event *ev, int *index)
+{
+ struct nostr_tag *tag;
+ int i;
+
+ for (i = 0; i < ev->num_tags; i++) {
+ tag = &ev->tags[i];
+ if (tag->num_elems == 2 && !strcmp(tag->strs[0], "nonce")) {
+ *index = i;
+ return 1;
+ }
+ }
+
+ *index = ev->num_tags;
+ return nostr_add_tag(ev, "nonce", "0");
+}
+
+static int mine_event(struct nostr_event *ev, int difficulty)
+{
+ char *strnonce = malloc(33);
+ struct nostr_tag *tag;
+ uint64_t nonce;
+ int index, res;
+
+ if (!ensure_nonce_tag(ev, &index))
+ return 0;
+
+ tag = &ev->tags[index];
+ assert(tag->num_elems == 2);
+ assert(!strcmp(tag->strs[0], "nonce"));
+ tag->strs[1] = strnonce;
+
+ for (nonce = 0;; nonce++) {
+ snprintf(strnonce, 32, "%" PRIu64, nonce);
+
+ if (!generate_event_id(ev))
+ return 0;
+
+ if ((res = count_leading_zero_bits(ev->id)) >= difficulty) {
+ fprintf(stderr, "mined %d bits\n", res);
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
static int make_encrypted_dm(secp256k1_context *ctx, struct key *key,
struct nostr_event *ev, unsigned char nostr_pubkey[32])
{
@@ -572,9 +631,16 @@ int main(int argc, const char *argv[])
// set the event's pubkey
memcpy(ev.pubkey, key.pubkey, 32);
- if (!generate_event_id(&ev)) {
- fprintf(stderr, "could not generate event id\n");
- return 5;
+ if (args.flags & HAS_DIFFICULTY) {
+ if (!mine_event(&ev, args.difficulty)) {
+ fprintf(stderr, "error when mining id\n");
+ return 22;
+ }
+ } else {
+ if (!generate_event_id(&ev)) {
+ fprintf(stderr, "could not generate event id\n");
+ return 5;
+ }
}
if (!sign_event(ctx, &key, &ev)) {
diff --git a/proof.h b/proof.h
@@ -0,0 +1,26 @@
+static inline int zero_bits(unsigned char b)
+{
+ int n = 0;
+
+ if (b == 0)
+ return 8;
+
+ while (b >>= 1)
+ n++;
+
+ return 7-n;
+}
+
+/* find the number of leading zero bits in a hash */
+static int count_leading_zero_bits(unsigned char *hash)
+{
+ int bits, total, i;
+
+ for (i = 0, total = 0; i < 32; i++) {
+ bits = zero_bits(hash[i]);
+ total += bits;
+ if (bits != 8)
+ break;
+ }
+ return total;
+}