commit cee688d5d14c1a3c20f08c2e78ea787997ee0216
parent 4e75ac71c34124e04b9e726e78418129cb159f4b
Author: William Casarin <jb55@jb55.com>
Date: Tue, 25 Jul 2023 12:04:51 -0700
add ndb_sign_id and ndb_calculate_id
also do this on finalization optionally
Diffstat:
9 files changed, 242 insertions(+), 43 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -9,3 +9,5 @@ test_contacts_ndb_note
bench
configurator
config.h
+libsecp256k1.a
+.direnv/
diff --git a/.gitmodules b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "deps/secp256k1"]
+ path = deps/secp256k1
+ url = https://github.com/bitcoin-core/secp256k1
diff --git a/Makefile b/Makefile
@@ -1,7 +1,11 @@
CFLAGS = -Wall -Wno-unused-function -Werror -O2 -g
-HEADERS = sha256.h nostrdb.h cursor.h hex.h jsmn.h config.h sha256.h
+HEADERS = sha256.h nostrdb.h cursor.h hex.h jsmn.h config.h sha256.h random.h
SRCS = nostrdb.c sha256.c
-DEPS = $(SRCS) $(HEADERS)
+LDS = $(SRCS) $(ARS)
+ARS = libsecp256k1.a
+DEPS = $(SRCS) $(HEADERS) $(ARS)
+PREFIX ?= /usr/local
+SUBMODULES = deps/secp256k1
check: test
./test
@@ -21,11 +25,31 @@ configurator: configurator.c
config.h: configurator
./configurator > $@
+deps/secp256k1/.git:
+ @devtools/refresh-submodules.sh $(SUBMODULES)
+
+deps/secp256k1/include/secp256k1.h: deps/secp256k1/.git
+
+deps/secp256k1/configure: deps/secp256k1/.git
+ cd deps/secp256k1; \
+ ./autogen.sh
+
+deps/secp256k1/.libs/libsecp256k1.a: deps/secp256k1/config.log
+ cd deps/secp256k1; \
+ make -j libsecp256k1.la
+
+deps/secp256k1/config.log: deps/secp256k1/configure
+ cd deps/secp256k1; \
+ ./configure --disable-shared --enable-module-ecdh --enable-module-schnorrsig --enable-module-extrakeys
+
+libsecp256k1.a: deps/secp256k1/.libs/libsecp256k1.a
+ cp $< $@
+
bench: bench.c $(DEPS)
- $(CC) $(CFLAGS) bench.c $(SRCS) -o $@
+ $(CC) $(CFLAGS) bench.c $(LDS) -o $@
test: test.c $(DEPS)
- $(CC) $(CFLAGS) test.c $(SRCS) -o $@
+ $(CC) $(CFLAGS) test.c $(LDS) -o $@
%.o: %.c
$(CC) $(CFLAGS)
diff --git a/deps/secp256k1 b/deps/secp256k1
@@ -0,0 +1 @@
+Subproject commit 2bd5f3e6184f1a4cfaed910ab74269fb6ab635be
diff --git a/devtools/refresh-submodules.sh b/devtools/refresh-submodules.sh
@@ -0,0 +1,42 @@
+#! /bin/sh
+
+if [ $# = 0 ]; then
+ echo "Usage: $0 <submoduledir1>..." >&2
+ exit 1
+fi
+
+# If no git dir (or, if we're a submodule, git file), forget it.
+[ -e .git ] || exit 0
+
+# git submodule can't run in parallel. Really.
+# Wait for it to finish if in parallel.
+if ! mkdir .refresh-submodules 2>/dev/null ; then
+ # If we don't make progress in ~60 seconds, force delete and retry.
+ LIMIT=$((50 + $$ % 20))
+ i=0
+ while [ $i -lt $LIMIT ]; do
+ [ -d .refresh-submodules ] || exit 0
+ sleep 1
+ i=$((i + 1))
+ done
+ rmdir .refresh-submodules
+ exec "$0" "$@" || exit 1
+fi
+
+trap "rmdir .refresh-submodules" EXIT
+
+# Be a little careful here, since we do rm -rf!
+for m in "$@"; do
+ if ! grep -q "path = $m\$" .gitmodules; then
+ echo "$m is not a submodule!" >&2
+ exit 1
+ fi
+done
+
+# git submodule can segfault. Really.
+if [ "$(git submodule status "$@" | grep -c '^ ')" != $# ]; then
+ echo Reinitializing submodules "$@" ...
+ git submodule sync "$@"
+ rm -rf "$@"
+ git submodule update --init --recursive "$@"
+fi
diff --git a/nostrdb.c b/nostrdb.c
@@ -3,6 +3,7 @@
#include "jsmn.h"
#include "hex.h"
#include "cursor.h"
+#include "random.h"
#include "sha256.h"
#include <stdlib.h>
#include <limits.h>
@@ -25,7 +26,6 @@ int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf,
int bufsize)
{
struct ndb_note *note;
- struct cursor mem;
int half, size, str_indices_size;
// come on bruh
@@ -39,14 +39,14 @@ int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf,
//debug("size %d half %d str_indices %d\n", size, half, str_indices_size);
// make a safe cursor of our available memory
- make_cursor(buf, buf + bufsize, &mem);
+ make_cursor(buf, buf + bufsize, &builder->mem);
note = builder->note = (struct ndb_note *)buf;
// take slices of the memory into subcursors
- if (!(cursor_slice(&mem, &builder->note_cur, half) &&
- cursor_slice(&mem, &builder->strings, half) &&
- cursor_slice(&mem, &builder->str_indices, str_indices_size))) {
+ if (!(cursor_slice(&builder->mem, &builder->note_cur, half) &&
+ cursor_slice(&builder->mem, &builder->strings, half) &&
+ cursor_slice(&builder->mem, &builder->str_indices, str_indices_size))) {
return 0;
}
@@ -265,7 +265,7 @@ static int ndb_event_commitment(struct ndb_note *ev, unsigned char *buf, int buf
return cur.p - cur.start;
}
-int ndb_calculate_note_id(struct ndb_note *note, unsigned char *buf, int buflen) {
+int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen) {
int len;
if (!(len = ndb_event_commitment(note, buf, buflen)))
@@ -278,12 +278,50 @@ int ndb_calculate_note_id(struct ndb_note *note, unsigned char *buf, int buflen)
return 1;
}
+int ndb_sign_id(secp256k1_context *ctx, struct ndb_keypair *keypair,
+ unsigned char id[32], unsigned char sig[64])
+{
+ unsigned char aux[32];
+
+ if (!fill_random(aux, sizeof(aux)))
+ return 0;
+
+ return secp256k1_schnorrsig_sign32(ctx, sig, id, &keypair->pair, aux);
+}
+
+int ndb_create_keypair(secp256k1_context *ctx, struct ndb_keypair *key)
+{
+ secp256k1_xonly_pubkey pubkey;
+
+ /* Try to create a keypair with a valid context, it should only
+ * fail if the secret key is zero or out of range. */
+ if (!secp256k1_keypair_create(ctx, &key->pair, key->secret))
+ return 0;
+
+ if (!secp256k1_keypair_xonly_pub(ctx, &pubkey, NULL, &key->pair))
+ return 0;
+
+ /* Serialize the public key. Should always return 1 for a valid public key. */
+ return secp256k1_xonly_pubkey_serialize(ctx, key->pubkey, &pubkey);
+}
-int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note)
+int ndb_decode_key(secp256k1_context *ctx, const char *secstr,
+ struct ndb_keypair *keypair)
+{
+ if (!hex_decode(secstr, strlen(secstr), keypair->secret, 32)) {
+ fprintf(stderr, "could not hex decode secret key\n");
+ return 0;
+ }
+
+ return ndb_create_keypair(ctx, keypair);
+}
+
+int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note,
+ struct ndb_keypair *keypair)
{
int strings_len = builder->strings.p - builder->strings.start;
- unsigned char *end = builder->note_cur.p + strings_len;
- int total_size = end - builder->note_cur.start;
+ unsigned char *note_end = builder->note_cur.p + strings_len;
+ int total_size = note_end - builder->note_cur.start;
// move the strings buffer next to the end of our ndb_note
memmove(builder->note_cur.p, builder->strings.start, strings_len);
@@ -296,6 +334,22 @@ int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note)
*note = builder->note;
+ // generate id and sign if we're building this manually
+ if (keypair) {
+ // use the remaining memory for building our id buffer
+ unsigned char *end = builder->mem.end;
+ unsigned char *start = (unsigned char*)(*note) + total_size;
+
+ if (!ndb_calculate_id(*note, start, end - start))
+ return 0;
+
+ secp256k1_context *ctx =
+ secp256k1_context_create(SECP256K1_CONTEXT_SIGN);
+
+ if (!ndb_sign_id(ctx, keypair, (*note)->id, (*note)->sig))
+ return 0;
+ }
+
return total_size;
}
@@ -626,7 +680,7 @@ int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
// sig
tok = &parser.toks[i+1];
hex_decode(json + tok->start, toksize(tok), hexbuf, sizeof(hexbuf));
- ndb_builder_set_signature(&parser.builder, hexbuf);
+ ndb_builder_set_sig(&parser.builder, hexbuf);
} else if (start[0] == 'k' && jsoneq(json, tok, tok_len, "kind")) {
// kind
tok = &parser.toks[i+1];
@@ -668,7 +722,7 @@ int ndb_note_from_json(const char *json, int len, struct ndb_note **note,
}
}
- return ndb_builder_finalize(&parser.builder, note);
+ return ndb_builder_finalize(&parser.builder, note, NULL);
}
void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey)
@@ -681,10 +735,9 @@ void ndb_builder_set_id(struct ndb_builder *builder, unsigned char *id)
memcpy(builder->note->id, id, 32);
}
-void ndb_builder_set_signature(struct ndb_builder *builder,
- unsigned char *signature)
+void ndb_builder_set_sig(struct ndb_builder *builder, unsigned char *sig)
{
- memcpy(builder->note->signature, signature, 64);
+ memcpy(builder->note->sig, sig, 64);
}
void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind)
diff --git a/nostrdb.h b/nostrdb.h
@@ -4,6 +4,10 @@
#include <inttypes.h>
#include "cursor.h"
+#include "secp256k1.h"
+#include "secp256k1_ecdh.h"
+#include "secp256k1_schnorrsig.h"
+
struct ndb_str {
unsigned char flag;
union {
@@ -12,6 +16,13 @@ struct ndb_str {
};
};
+struct ndb_keypair {
+ unsigned char pubkey[32];
+ unsigned char secret[32];
+ secp256k1_keypair pair;
+};
+
+
// these must be byte-aligned, they are directly accessing the serialized data
// representation
#pragma pack(push, 1)
@@ -47,7 +58,7 @@ struct ndb_note {
unsigned char padding[3]; // keep things aligned
unsigned char id[32];
unsigned char pubkey[32];
- unsigned char signature[64];
+ unsigned char sig[64];
uint32_t created_at;
uint32_t kind;
@@ -62,6 +73,7 @@ struct ndb_note {
#pragma pack(pop)
struct ndb_builder {
+ struct cursor mem;
struct cursor note_cur;
struct cursor strings;
struct cursor str_indices;
@@ -78,22 +90,23 @@ struct ndb_iterator {
};
// HELPERS
-int ndb_calculate_note_id(struct ndb_note *note, unsigned char *buf, int buflen);
-// BYE HELPERS
+int ndb_calculate_id(struct ndb_note *note, unsigned char *buf, int buflen);
+int ndb_sign_id(secp256k1_context *ctx, struct ndb_keypair *keypair, unsigned char id[32], unsigned char sig[64]);
+int ndb_create_keypair(secp256k1_context *ctx, struct ndb_keypair *key);
+int ndb_decode_key(secp256k1_context *ctx, const char *secstr, struct ndb_keypair *keypair);
-// HI BUILDER
+// BUILDER
int ndb_note_from_json(const char *json, int len, struct ndb_note **, unsigned char *buf, int buflen);
int ndb_builder_init(struct ndb_builder *builder, unsigned char *buf, int bufsize);
-int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note);
+int ndb_builder_finalize(struct ndb_builder *builder, struct ndb_note **note, struct ndb_keypair *privkey);
int ndb_builder_set_content(struct ndb_builder *builder, const char *content, int len);
void ndb_builder_set_created_at(struct ndb_builder *builder, uint32_t created_at);
-void ndb_builder_set_signature(struct ndb_builder *builder, unsigned char *signature);
+void ndb_builder_set_sig(struct ndb_builder *builder, unsigned char *sig);
void ndb_builder_set_pubkey(struct ndb_builder *builder, unsigned char *pubkey);
void ndb_builder_set_id(struct ndb_builder *builder, unsigned char *id);
void ndb_builder_set_kind(struct ndb_builder *builder, uint32_t kind);
int ndb_builder_new_tag(struct ndb_builder *builder);
int ndb_builder_push_tag_str(struct ndb_builder *builder, const char *str, int len);
-// BYE BUILDER
static inline struct ndb_str ndb_note_str(struct ndb_note *note,
union ndb_packed_str *pstr)
@@ -116,17 +129,6 @@ static inline struct ndb_str ndb_tag_str(struct ndb_note *note,
return ndb_note_str(note, &tag->strs[ind]);
}
-static inline int ndb_tag_matches_char(struct ndb_note *note,
- struct ndb_tag *tag, int ind, char c)
-{
- struct ndb_str str = ndb_tag_str(note, tag, ind);
- if (str.str[0] == '\0')
- return 0;
- else if (str.str[0] == c)
- return 1;
- return 0;
-}
-
static inline struct ndb_str ndb_iter_tag_str(struct ndb_iterator *iter,
int ind)
{
@@ -143,9 +145,9 @@ static inline unsigned char * ndb_note_pubkey(struct ndb_note *note)
return note->pubkey;
}
-static inline unsigned char * ndb_note_signature(struct ndb_note *note)
+static inline unsigned char * ndb_note_sig(struct ndb_note *note)
{
- return note->signature;
+ return note->sig;
}
static inline uint32_t ndb_note_created_at(struct ndb_note *note)
diff --git a/random.h b/random.h
@@ -0,0 +1,72 @@
+/*************************************************************************
+ * Copyright (c) 2020-2021 Elichai Turkel *
+ * Distributed under the CC0 software license, see the accompanying file *
+ * EXAMPLES_COPYING or https://creativecommons.org/publicdomain/zero/1.0 *
+ *************************************************************************/
+
+/*
+ * This file is an attempt at collecting best practice methods for obtaining randomness with different operating systems.
+ * It may be out-of-date. Consult the documentation of the operating system before considering to use the methods below.
+ *
+ * Platform randomness sources:
+ * Linux -> `getrandom(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. http://man7.org/linux/man-pages/man2/getrandom.2.html, https://linux.die.net/man/4/urandom
+ * macOS -> `getentropy(2)`(`sys/random.h`), if not available `/dev/urandom` should be used. https://www.unix.com/man-page/mojave/2/getentropy, https://opensource.apple.com/source/xnu/xnu-517.12.7/bsd/man/man4/random.4.auto.html
+ * FreeBSD -> `getrandom(2)`(`sys/random.h`), if not available `kern.arandom` should be used. https://www.freebsd.org/cgi/man.cgi?query=getrandom, https://www.freebsd.org/cgi/man.cgi?query=random&sektion=4
+ * OpenBSD -> `getentropy(2)`(`unistd.h`), if not available `/dev/urandom` should be used. https://man.openbsd.org/getentropy, https://man.openbsd.org/urandom
+ * Windows -> `BCryptGenRandom`(`bcrypt.h`). https://docs.microsoft.com/en-us/windows/win32/api/bcrypt/nf-bcrypt-bcryptgenrandom
+ */
+
+#if defined(_WIN32)
+#include <windows.h>
+#include <ntstatus.h>
+#include <bcrypt.h>
+#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__)
+#include <sys/random.h>
+#elif defined(__OpenBSD__)
+#include <unistd.h>
+#else
+#error "Couldn't identify the OS"
+#endif
+
+#include <stddef.h>
+#include <limits.h>
+#include <stdio.h>
+
+
+/* Returns 1 on success, and 0 on failure. */
+static int fill_random(unsigned char* data, size_t size) {
+#if defined(_WIN32)
+ NTSTATUS res = BCryptGenRandom(NULL, data, size, BCRYPT_USE_SYSTEM_PREFERRED_RNG);
+ if (res != STATUS_SUCCESS || size > ULONG_MAX) {
+ return 0;
+ } else {
+ return 1;
+ }
+#elif defined(__linux__) || defined(__FreeBSD__)
+ /* If `getrandom(2)` is not available you should fallback to /dev/urandom */
+ ssize_t res = getrandom(data, size, 0);
+ if (res < 0 || (size_t)res != size ) {
+ return 0;
+ } else {
+ return 1;
+ }
+#elif defined(__APPLE__) || defined(__OpenBSD__)
+ /* If `getentropy(2)` is not available you should fallback to either
+ * `SecRandomCopyBytes` or /dev/urandom */
+ int res = getentropy(data, size);
+ if (res == 0) {
+ return 1;
+ } else {
+ return 0;
+ }
+#endif
+ return 0;
+}
+
+static void print_hex(unsigned char* data, size_t size) {
+ size_t i;
+ for (i = 0; i < size; i++) {
+ fprintf(stderr, "%02x", data[i]);
+ }
+ fprintf(stderr, "\n");
+}
diff --git a/test.c b/test.c
@@ -34,7 +34,7 @@ static void test_basic_event() {
ok = ndb_builder_set_content(b, hex_pk, strlen(hex_pk)); assert(ok);
ndb_builder_set_id(b, id); assert(ok);
ndb_builder_set_pubkey(b, pubkey); assert(ok);
- ndb_builder_set_signature(b, sig); assert(ok);
+ ndb_builder_set_sig(b, sig); assert(ok);
ok = ndb_builder_new_tag(b); assert(ok);
ok = ndb_builder_push_tag_str(b, "p", 1); assert(ok);
@@ -45,7 +45,7 @@ static void test_basic_event() {
ok = ndb_builder_push_tag_str(b, "words", 5); assert(ok);
ok = ndb_builder_push_tag_str(b, "w", 1); assert(ok);
- ok = ndb_builder_finalize(b, ¬e);
+ ok = ndb_builder_finalize(b, ¬e, NULL);
assert(ok);
// content should never be packed id
@@ -90,7 +90,7 @@ static void test_empty_tags() {
ok = ndb_builder_init(b, buf, sizeof(buf));
assert(ok);
- ok = ndb_builder_finalize(b, ¬e);
+ ok = ndb_builder_finalize(b, ¬e, NULL);
assert(ok);
assert(note->tags.count == 0);
@@ -131,7 +131,7 @@ static void test_parse_contact_list()
memcpy(id, note->id, 32);
memset(note->id, 0, 32);
- assert(ndb_calculate_note_id(note, json, alloc_size));
+ assert(ndb_calculate_id(note, json, alloc_size));
assert(!memcmp(note->id, id, 32));
const char* expected_content =
@@ -241,7 +241,7 @@ static void test_strings_work_before_finalization() {
ndb_builder_set_content(b, "hello", 5);
assert(!strcmp(ndb_note_str(b->note, &b->note->content).str, "hello"));
- assert(ndb_builder_finalize(b, ¬e));
+ assert(ndb_builder_finalize(b, ¬e, NULL));
assert(!strcmp(ndb_note_str(b->note, &b->note->content).str, "hello"));
}