nostrdb

an unfairly fast embedded nostr database backed by lmdb
git clone git://jb55.com/nostrdb
Log | Files | Refs | Submodules | README | LICENSE

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:
M.gitignore | 2++
A.gitmodules | 3+++
MMakefile | 32++++++++++++++++++++++++++++----
Adeps/secp256k1 | 1+
Adevtools/refresh-submodules.sh | 42++++++++++++++++++++++++++++++++++++++++++
Mnostrdb.c | 81+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------
Mnostrdb.h | 42++++++++++++++++++++++--------------------
Arandom.h | 72++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest.c | 10+++++-----
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, &note); + ok = ndb_builder_finalize(b, &note, 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, &note); + ok = ndb_builder_finalize(b, &note, 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, &note)); + assert(ndb_builder_finalize(b, &note, NULL)); assert(!strcmp(ndb_note_str(b->note, &b->note->content).str, "hello")); }