commit 6aa05ec5c34ec53a7e882ddbed2f50ab0857ee15
parent e754b81511ebe2eaf996282e7c3d40644317caa6
Author: William Casarin <jb55@jb55.com>
Date: Thu, 27 Nov 2025 07:21:43 -0800
initial giftwrap support
Signed-off-by: William Casarin <jb55@jb55.com>
Diffstat:
| M | .gitignore | | | 1 | + |
| M | .gitmodules | | | 3 | +++ |
| M | Makefile | | | 22 | ++++++++++++++++------ |
| M | cursor.h | | | 66 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | deps/libsodium | | | 1 | + |
| M | hex.h | | | 4 | ++++ |
| A | hkdf_sha256.c | | | 104 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | hkdf_sha256.h | | | 28 | ++++++++++++++++++++++++++++ |
| A | hmac_sha256.c | | | 161 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | hmac_sha256.h | | | 86 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | nip44.c | | | 466 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| A | nip44.h | | | 57 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
| M | nostril.c | | | 241 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------- |
| M | shell.nix | | | 1 | + |
14 files changed, 1195 insertions(+), 46 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -3,6 +3,7 @@
.privenv
nostril
/result
+build.log
configurator.out*
configurator
.build-result
diff --git a/.gitmodules b/.gitmodules
@@ -1,3 +1,6 @@
[submodule "deps/secp256k1"]
path = deps/secp256k1
url = https://github.com/bitcoin-core/secp256k1
+[submodule "deps/libsodium"]
+ path = deps/libsodium
+ url = https://github.com/jedisct1/libsodium.git
diff --git a/Makefile b/Makefile
@@ -1,11 +1,13 @@
-CFLAGS = -Wall -O2 -Ideps/secp256k1/include
-OBJS = sha256.o nostril.o aes.o base64.o
-HEADERS = hex.h random.h config.h sha256.h deps/secp256k1/include/secp256k1.h
+CFLAGS = -Wall -O2 -Ideps/secp256k1/include -Ideps/libsodium/src/libsodium/include/
+LDFLAGS = -lm
+OBJS = sha256.o nostril.o aes.o base64.o nip44.o hmac_sha256.o hkdf_sha256.o
+HEADERS = hex.h nip44.h cursor.h random.h config.h sha256.h deps/secp256k1/include/secp256k1.h
PREFIX ?= /usr/local
-ARS = libsecp256k1.a
+LIBSODIUM_AR=deps/libsodium/src/libsodium/.libs/libsodium.a
+ARS = libsecp256k1.a $(LIBSODIUM_AR)
-SUBMODULES = deps/secp256k1
+SUBMODULES = deps/secp256k1 deps/libsodium
all: nostril docs
@@ -27,6 +29,14 @@ dist: docs version
cp CHANGELOG dist/CHANGELOG.txt
rsync -avzP dist/ charon:/www/cdn.jb55.com/tarballs/nostril/
+$(LIBSODIUM_AR): deps/libsodium/config.log
+ cd deps/libsodium/src/libsodium; \
+ make -j libsodium.la
+
+deps/libsodium/config.log: deps/libsodium/configure
+ cd deps/libsodium; \
+ ./configure --disable-shared --enable-minimal
+
deps/secp256k1/.git:
@devtools/refresh-submodules.sh $(SUBMODULES)
@@ -52,7 +62,7 @@ libsecp256k1.a: deps/secp256k1/.libs/libsecp256k1.a
@$(CC) $(CFLAGS) -c $< -o $@
nostril: $(HEADERS) $(OBJS) $(ARS)
- $(CC) $(CFLAGS) $(OBJS) $(ARS) -o $@
+ $(CC) $(CFLAGS) $(LDFLAGS) $(OBJS) $(ARS) -o $@
install: all
mkdir -p $(PREFIX)/share/man/man1
diff --git a/cursor.h b/cursor.h
@@ -5,6 +5,7 @@
#include <stdio.h>
#include <assert.h>
#include <string.h>
+#include "hex.h"
#define unlikely(x) __builtin_expect((x),0)
#define likely(x) __builtin_expect((x),1)
@@ -254,11 +255,42 @@ static inline int cursor_pull_int(struct cursor *cursor, int *i)
return cursor_pull(cursor, (unsigned char*)i, sizeof(*i));
}
+static inline int cursor_pull_u16(struct cursor *cursor, uint16_t *i)
+{
+ return cursor_pull(cursor, (unsigned char*)i, sizeof(*i));
+}
+
+#define BSWAP_16(val) \
+ ((((uint16_t)(val) & 0x00ff) << 8) \
+ | (((uint16_t)(val) & 0xff00) >> 8))
+
+static inline uint16_t bswap_16(uint16_t val)
+{
+ return BSWAP_16(val);
+}
+
static inline int cursor_push_u16(struct cursor *cursor, unsigned short i)
{
return cursor_push(cursor, (unsigned char*)&i, sizeof(i));
}
+static int cursor_pull_b16(struct cursor *c, uint16_t *s)
+{
+ if (!cursor_pull_u16(c, s))
+ return 0;
+
+ // we assume little endian
+ *s = bswap_16(*s);
+ return 1;
+}
+
+static int cursor_push_b16(struct cursor *c, uint16_t s)
+{
+ if (!cursor_push_u16(c, bswap_16(s)))
+ return 0;
+ return 1;
+}
+
static inline void *index_cursor(struct cursor *cursor, unsigned int index, int elem_size)
{
unsigned char *p;
@@ -291,6 +323,16 @@ static inline int cursor_remaining_capacity(struct cursor *cursor)
return cursor->end - cursor->p;
}
+static inline int cursor_push_hex(struct cursor *c, const void *buf, size_t bufsize)
+{
+ int size;
+ size = hex_encode(buf, bufsize, (char *)c->p, c->end - c->p);
+ if (!size)
+ return 0;
+ c->p += bufsize * 2;
+ return 1;
+}
+
#define max(a,b) ((a) > (b) ? (a) : (b))
static inline void cursor_print_around(struct cursor *cur, int range)
@@ -317,4 +359,28 @@ static inline void cursor_print_around(struct cursor *cur, int range)
}
#undef max
+static inline int cursor_memset(struct cursor *cursor, unsigned char c, int n)
+{
+ if (cursor->p + n >= cursor->end)
+ return 0;
+
+ memset(cursor->p, c, n);
+ cursor->p += n;
+
+ return 1;
+}
+
+
+static inline int cursor_align(struct cursor *cur, int bytes) {
+ size_t size = cur->p - cur->start;
+ int pad;
+
+ // pad to n-byte alignment
+ pad = ((size + (bytes-1)) & ~(bytes-1)) - size;
+ if (pad > 0 && !cursor_memset(cur, 0, pad))
+ return 0;
+
+ return 1;
+}
+
#endif
diff --git a/deps/libsodium b/deps/libsodium
@@ -0,0 +1 @@
+Subproject commit 9511c982fb1d046470a8b42aa36556cdb7da15de
diff --git a/hex.h b/hex.h
@@ -1,4 +1,7 @@
+#ifndef NOSTRIL_HEX_H
+#define NOSTRIL_HEX_H
+
static inline int char_to_hex(unsigned char *val, char c)
{
if (c >= '0' && c <= '9') {
@@ -67,3 +70,4 @@ static inline int hex_encode(const void *buf, size_t bufsize, char *dest, size_t
return 1;
}
+#endif /* NOSTRIL_HEX_H */
diff --git a/hkdf_sha256.c b/hkdf_sha256.c
@@ -0,0 +1,104 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#include "hkdf_sha256.h"
+#include "hmac_sha256.h"
+#include <assert.h>
+#include <string.h>
+
+void hkdf_expand(void *okm, size_t okm_size,
+ const void *prk, size_t prksize,
+ const void *info, size_t isize)
+{
+ struct hmac_sha256_ctx ctx;
+ struct hmac_sha256 t;
+ unsigned char c;
+
+ assert(okm_size < 255 * sizeof(t));
+ /*
+ * 2.3. Step 2: Expand
+ *
+ * HKDF-Expand(PRK, info, L) -> OKM
+ *
+ * Options:
+ * Hash a hash function; HashLen denotes the length of the
+ * hash function output in octets
+ *
+ * Inputs:
+ * PRK a pseudorandom key of at least HashLen octets
+ * (usually, the output from the extract step)
+ * info optional context and application specific information
+ * (can be a zero-length string)
+ * L length of output keying material in octets
+ * (<= 255*HashLen)
+ *
+ * Output:
+ * OKM output keying material (of L octets)
+ *
+ * The output OKM is calculated as follows:
+ *
+ * N = ceil(L/HashLen)
+ * T = T(1) | T(2) | T(3) | ... | T(N)
+ * OKM = first L octets of T
+ *
+ * where:
+ * T(0) = empty string (zero length)
+ * T(1) = HMAC-Hash(PRK, T(0) | info | 0x01)
+ * T(2) = HMAC-Hash(PRK, T(1) | info | 0x02)
+ * T(3) = HMAC-Hash(PRK, T(2) | info | 0x03)
+ * ...
+ *
+ * (where the constant concatenated to the end of each T(n) is a
+ * single octet.)
+ */
+ c = 1;
+ hmac_sha256_init(&ctx, prk, prksize);
+ hmac_sha256_update(&ctx, info, isize);
+ hmac_sha256_update(&ctx, &c, 1);
+ hmac_sha256_done(&ctx, &t);
+
+ while (okm_size > sizeof(t)) {
+ memcpy(okm, &t, sizeof(t));
+ okm = (char *)okm + sizeof(t);
+ okm_size -= sizeof(t);
+
+ c++;
+ hmac_sha256_init(&ctx, prk, prksize);
+ hmac_sha256_update(&ctx, &t, sizeof(t));
+ hmac_sha256_update(&ctx, info, isize);
+ hmac_sha256_update(&ctx, &c, 1);
+ hmac_sha256_done(&ctx, &t);
+ }
+ memcpy(okm, &t, okm_size);
+}
+
+void hkdf_sha256(void *okm, size_t okm_size,
+ const void *s, size_t ssize,
+ const void *k, size_t ksize,
+ const void *info, size_t isize)
+{
+ struct hmac_sha256 prk;
+
+ /* RFC 5869:
+ *
+ * 2.2. Step 1: Extract
+ *
+ * HKDF-Extract(salt, IKM) -> PRK
+ *
+ * Options:
+ * Hash a hash function; HashLen denotes the length of the
+ * hash function output in octets
+ *
+ * Inputs:
+ * salt optional salt value (a non-secret random value);
+ * if not provided, it is set to a string of HashLen zeros.
+ * IKM input keying material
+ *
+ * Output:
+ * PRK a pseudorandom key (of HashLen octets)
+ *
+ * The output PRK is calculated as follows:
+ *
+ * PRK = HMAC-Hash(salt, IKM)
+ */
+ hmac_sha256(&prk, s, ssize, k, ksize);
+ hkdf_expand(okm, okm_size, &prk, sizeof(prk), info, isize);
+}
diff --git a/hkdf_sha256.h b/hkdf_sha256.h
@@ -0,0 +1,28 @@
+#ifndef CCAN_CRYPTO_HKDF_SHA256_H
+#define CCAN_CRYPTO_HKDF_SHA256_H
+/* BSD-MIT - see LICENSE file for details */
+#include "config.h"
+#include "hmac_sha256.h"
+#include <stdlib.h>
+
+/**
+ * hkdf_sha256 - generate a derived key
+ * @okm: where to output the key
+ * @okm_size: the number of bytes pointed to by @okm (must be less than 255*32)
+ * @s: salt
+ * @ssize: the number of bytes pointed to by @s
+ * @k: pointer to input key
+ * @ksize: the number of bytes pointed to by @k
+ * @info: pointer to info
+ * @isize: the number of bytes pointed to by @info
+ */
+void hkdf_sha256(void *okm, size_t okm_size,
+ const void *s, size_t ssize,
+ const void *k, size_t ksize,
+ const void *info, size_t isize);
+
+void hkdf_expand(void *okm, size_t okm_size,
+ const void *prk, size_t prksize,
+ const void *info, size_t isize);
+
+#endif /* CCAN_CRYPTO_HKDF_SHA256_H */
diff --git a/hmac_sha256.c b/hmac_sha256.c
@@ -0,0 +1,161 @@
+/* MIT (BSD) license - see LICENSE file for details */
+#include "hmac_sha256.h"
+#include <string.h>
+
+#define IPAD 0x3636363636363636ULL
+#define OPAD 0x5C5C5C5C5C5C5C5CULL
+
+#define BLOCK_U64S (HMAC_SHA256_BLOCKSIZE / sizeof(uint64_t))
+
+static inline void xor_block(uint64_t block[BLOCK_U64S], uint64_t pad)
+{
+ size_t i;
+
+ for (i = 0; i < BLOCK_U64S; i++)
+ block[i] ^= pad;
+}
+
+void hmac_sha256_init(struct hmac_sha256_ctx *ctx,
+ const void *k, size_t ksize)
+{
+ struct sha256 hashed_key;
+ /* We use k_opad as k_ipad temporarily. */
+ uint64_t *k_ipad = ctx->k_opad;
+
+ /* (keys longer than B bytes are first hashed using H) */
+ if (ksize > HMAC_SHA256_BLOCKSIZE) {
+ sha256(&hashed_key, k, ksize);
+ k = &hashed_key;
+ ksize = sizeof(hashed_key);
+ }
+
+ /* From RFC2104:
+ *
+ * (1) append zeros to the end of K to create a B byte string
+ * (e.g., if K is of length 20 bytes and B=64, then K will be
+ * appended with 44 zero bytes 0x00)
+ */
+ if (ksize != 0)
+ memcpy(k_ipad, k, ksize);
+ memset((char *)k_ipad + ksize, 0, HMAC_SHA256_BLOCKSIZE - ksize);
+
+ /*
+ * (2) XOR (bitwise exclusive-OR) the B byte string computed
+ * in step (1) with ipad
+ */
+ xor_block(k_ipad, IPAD);
+
+ /*
+ * We start (4) here, appending text later:
+ *
+ * (3) append the stream of data 'text' to the B byte string resulting
+ * from step (2)
+ * (4) apply H to the stream generated in step (3)
+ */
+ sha256_init(&ctx->sha);
+ sha256_update(&ctx->sha, k_ipad, HMAC_SHA256_BLOCKSIZE);
+
+ /*
+ * (5) XOR (bitwise exclusive-OR) the B byte string computed in
+ * step (1) with opad
+ */
+ xor_block(ctx->k_opad, IPAD^OPAD);
+}
+
+void hmac_sha256_update(struct hmac_sha256_ctx *ctx, const void *p, size_t size)
+{
+ /* This is the appending-text part of this:
+ *
+ * (3) append the stream of data 'text' to the B byte string resulting
+ * from step (2)
+ * (4) apply H to the stream generated in step (3)
+ */
+ sha256_update(&ctx->sha, p, size);
+}
+
+void hmac_sha256_done(struct hmac_sha256_ctx *ctx,
+ struct hmac_sha256 *hmac)
+{
+ /* (4) apply H to the stream generated in step (3) */
+ sha256_done(&ctx->sha, &hmac->sha);
+
+ /*
+ * (6) append the H result from step (4) to the B byte string
+ * resulting from step (5)
+ * (7) apply H to the stream generated in step (6) and output
+ * the result
+ */
+ sha256_init(&ctx->sha);
+ sha256_update(&ctx->sha, ctx->k_opad, sizeof(ctx->k_opad));
+ sha256_update(&ctx->sha, &hmac->sha, sizeof(hmac->sha));
+ sha256_done(&ctx->sha, &hmac->sha);
+}
+
+#if 1
+void hmac_sha256(struct hmac_sha256 *hmac,
+ const void *k, size_t ksize,
+ const void *d, size_t dsize)
+{
+ struct hmac_sha256_ctx ctx;
+
+ hmac_sha256_init(&ctx, k, ksize);
+ hmac_sha256_update(&ctx, d, dsize);
+ hmac_sha256_done(&ctx, hmac);
+}
+#else
+/* Direct mapping from MD5 example in RFC2104 */
+void hmac_sha256(struct hmac_sha256 *hmac,
+ const void *key, size_t key_len,
+ const void *text, size_t text_len)
+{
+ struct sha256_ctx context;
+ unsigned char k_ipad[65]; /* inner padding -
+ * key XORd with ipad
+ */
+ unsigned char k_opad[65]; /* outer padding -
+ * key XORd with opad
+ *//* start out by storing key in pads */
+ unsigned char tk[32];
+ int i;
+
+ /* if key is longer than 64 bytes reset it to key=MD5(key) */
+ if (key_len > 64) {
+
+ struct sha256_ctx tctx;
+
+ sha256_init(&tctx);
+ sha256_update(&tctx, key, key_len);
+ sha256_done(&tctx, tk);
+
+ key = tk;
+ key_len = 32;
+ }
+ bzero( k_ipad, sizeof k_ipad);
+ bzero( k_opad, sizeof k_opad);
+ bcopy( key, k_ipad, key_len);
+ bcopy( key, k_opad, key_len);
+
+ /* XOR key with ipad and opad values */
+ for (i=0; i<64; i++) {
+ k_ipad[i] ^= 0x36;
+ k_opad[i] ^= 0x5c;
+ }
+ /*
+ * perform inner MD5
+ */
+ sha256_init(&context); /* init context for 1st
+ * pass */
+ sha256_update(&context, k_ipad, 64); /* start with inner pad */
+ sha256_update(&context, text, text_len); /* then text of datagram */
+ sha256_done(&context, &hmac->sha); /* finish up 1st pass */
+ /*
+ * perform outer MD5
+ */
+ sha256_init(&context); /* init context for 2nd
+ * pass */
+ sha256_update(&context, k_opad, 64); /* start with outer pad */
+ sha256_update(&context, &hmac->sha, 32); /* then results of 1st
+ * hash */
+ sha256_done(&context, &hmac->sha); /* finish up 2nd pass */
+}
+#endif
diff --git a/hmac_sha256.h b/hmac_sha256.h
@@ -0,0 +1,86 @@
+#ifndef CCAN_CRYPTO_HMAC_SHA256_H
+#define CCAN_CRYPTO_HMAC_SHA256_H
+/* BSD-MIT - see LICENSE file for details */
+#include "config.h"
+#include <stdint.h>
+#include <stdlib.h>
+#include "sha256.h"
+
+/* Number of bytes per block. */
+#define HMAC_SHA256_BLOCKSIZE 64
+
+/**
+ * struct hmac_sha256 - structure representing a completed HMAC.
+ */
+struct hmac_sha256 {
+ struct sha256 sha;
+};
+
+/**
+ * hmac_sha256 - return hmac of an object with a key.
+ * @hmac: the hmac to fill in
+ * @k: pointer to the key,
+ * @ksize: the number of bytes pointed to by @k
+ * @d: pointer to memory,
+ * @dsize: the number of bytes pointed to by @d
+ */
+void hmac_sha256(struct hmac_sha256 *hmac,
+ const void *k, size_t ksize,
+ const void *d, size_t dsize);
+
+/**
+ * struct hmac_sha256_ctx - structure to store running context for hmac_sha256
+ */
+struct hmac_sha256_ctx {
+ struct sha256_ctx sha;
+ uint64_t k_opad[HMAC_SHA256_BLOCKSIZE / sizeof(uint64_t)];
+};
+
+/**
+ * hmac_sha256_init - initialize an HMAC_SHA256 context.
+ * @ctx: the hmac_sha256_ctx to initialize
+ * @k: pointer to the key,
+ * @ksize: the number of bytes pointed to by @k
+ *
+ * This must be called before hmac_sha256_update or hmac_sha256_done.
+ *
+ * If it was already initialized, this forgets anything which was
+ * hashed before.
+ *
+ * Example:
+ * static void hmac_all(const char *key,
+ * const char **arr, struct hmac_sha256 *hash)
+ * {
+ * size_t i;
+ * struct hmac_sha256_ctx ctx;
+ *
+ * hmac_sha256_init(&ctx, key, strlen(key));
+ * for (i = 0; arr[i]; i++)
+ * hmac_sha256_update(&ctx, arr[i], strlen(arr[i]));
+ * hmac_sha256_done(&ctx, hash);
+ * }
+ */
+void hmac_sha256_init(struct hmac_sha256_ctx *ctx,
+ const void *k, size_t ksize);
+
+/**
+ * hmac_sha256_update - include some memory in the hash.
+ * @ctx: the hmac_sha256_ctx to use
+ * @p: pointer to memory,
+ * @size: the number of bytes pointed to by @p
+ *
+ * You can call this multiple times to hash more data, before calling
+ * hmac_sha256_done().
+ */
+void hmac_sha256_update(struct hmac_sha256_ctx *ctx, const void *p, size_t size);
+
+/**
+ * hmac_sha256_done - finish HMAC_SHA256 and return the hash
+ * @ctx: the hmac_sha256_ctx to complete
+ * @res: the hash to return.
+ *
+ * Note that @ctx is *destroyed* by this, and must be reinitialized.
+ * To avoid that, pass a copy instead.
+ */
+void hmac_sha256_done(struct hmac_sha256_ctx *hmac_sha256, struct hmac_sha256 *res);
+#endif /* CCAN_CRYPTO_HMAC_SHA256_H */
diff --git a/nip44.c b/nip44.c
@@ -0,0 +1,466 @@
+
+#include "base64.h"
+#include "secp256k1.h"
+#include "secp256k1_ecdh.h"
+#include "secp256k1_schnorrsig.h"
+#include "hmac_sha256.h"
+#include "hkdf_sha256.h"
+#include "nip44.h"
+#include "random.h"
+#include "cursor.h"
+#include "sodium/crypto_stream_chacha20.h"
+#include <string.h>
+
+/* NIP44 payload encryption/decryption */
+
+static int copyx(unsigned char *output, const unsigned char *x32,
+ const unsigned char *y32, void *data)
+{
+ memcpy(output, x32, 32);
+ return 1;
+}
+
+static enum ndb_decrypt_result
+calculate_shared_secret(secp256k1_context *ctx,
+ const unsigned char *seckey,
+ const unsigned char *pubkey,
+ unsigned char *shared_secret)
+{
+ secp256k1_pubkey parsed_pubkey;
+ unsigned char compressed_pubkey[33];
+ compressed_pubkey[0] = 2;
+ memcpy(&compressed_pubkey[1], pubkey, 32);
+
+ if (!secp256k1_ec_seckey_verify(ctx, seckey)) {
+ return NIP44_ERR_SECKEY_VERIFY_FAILED;
+ }
+
+ if (!secp256k1_ec_pubkey_parse(ctx, &parsed_pubkey, compressed_pubkey, sizeof(compressed_pubkey))) {
+ return NIP44_ERR_PUBKEY_PARSE_FAILED;
+ }
+
+ if (!secp256k1_ecdh(ctx, shared_secret, &parsed_pubkey, seckey, copyx, NULL)) {
+ return NIP44_ERR_ECDH_FAILED;
+ }
+
+ return NIP44_OK;
+}
+
+struct message_keys {
+ unsigned char key[32];
+ unsigned char nonce[12];
+ unsigned char auth[32];
+};
+
+static void hmac_aad(struct hmac_sha256 *out,
+ unsigned char hmac[32], unsigned char *aad,
+ const unsigned char *msg, size_t msgsize)
+{
+ struct hmac_sha256_ctx ctx;
+ hmac_sha256_init(&ctx, hmac, 32);
+ hmac_sha256_update(&ctx, aad, 32);
+ hmac_sha256_update(&ctx, msg, msgsize);
+ hmac_sha256_done(&ctx, out);
+}
+
+enum ndb_decrypt_result
+nip44_decode_payload(struct nip44_payload *decoded,
+ unsigned char *buf, size_t bufsize,
+ const char *payload, size_t payload_len)
+{
+ size_t decoded_len;
+
+ /* NOTE(jb55): we use the variant that doesn't have an
+ * upper size limit
+ */
+ if (payload_len < 132 /*|| plen > 87472*/) {
+ return NIP44_ERR_INVALID_PAYLOAD;
+ }
+
+ /*
+ 1. Check if first payload's character is `#`
+
+ - `#` is an optional future-proof flag that means non-base64
+ encoding is used
+
+ - The `#` is not present in base64 alphabet, but, instead of
+ throwing `base64 is invalid`, implementations MUST indicate that
+ the encryption version is not yet supported
+ */
+ if (payload[0] == '#') {
+ return NIP44_ERR_UNSUPPORTED_ENCODING;
+ }
+
+ /*
+ 2. Decode base64
+ - Base64 is decoded into `version, nonce, ciphertext, mac`
+
+ - If the version is unknown, implementations must indicate that the
+ encryption version is not supported
+
+ - Validate length of base64 message to prevent DoS on base64
+ decoder: it can be in range from 132 to 87472 chars
+
+ - Validate length of decoded message to verify output of the
+ decoder: it can be in range from 99 to 65603 bytes
+ */
+ decoded_len = base64_decode((char*)buf, bufsize, payload, payload_len);
+ if (decoded_len == -1) {
+ return NIP44_ERR_BASE64_DECODE;
+ } else if (decoded_len < 99 /*|| decoded_len > 65603*/) {
+ return NIP44_ERR_INVALID_PAYLOAD;
+ }
+
+ decoded->version = buf[0];
+ decoded->nonce = &buf[1];
+ decoded->ciphertext = &buf[33];
+ decoded->ciphertext_len = decoded_len - 65;
+ decoded->mac = &buf[decoded_len-32];
+
+ return NIP44_OK;
+}
+
+static inline uint16_t next_pow2_16(uint16_t v)
+{
+ if (v <= 1)
+ return 1;
+
+ v--; /* round down from v to (v-1) */
+ v |= v >> 1;
+ v |= v >> 2;
+ v |= v >> 4;
+ v |= v >> 8;
+ v++; /* now v is next power of two */
+
+ return v;
+}
+
+static int calc_padded_len(uint16_t unpadded_len)
+{
+ uint16_t chunk;
+
+ /* enforce minimum of 32 */
+ if (unpadded_len <= 32)
+ return 32;
+
+ /* For <= 256, always use 32-byte chunks. */
+ if (unpadded_len <= 256) {
+ chunk = 32;
+ } else {
+ /* next_power / 8 */
+ chunk = next_pow2_16(unpadded_len) >> 3;
+ }
+
+ chunk--;
+
+ // Round up to the next multiple of chunk (chunk is power of two)
+ return (unpadded_len + chunk) & ~chunk;
+}
+
+static int unpad(unsigned char *padded_buf, size_t len, uint16_t *unpadded_len)
+{
+ struct cursor c;
+ unsigned char *decoded_end;
+ uint16_t decoded_len, decoded_end_len;
+
+ make_cursor(padded_buf, padded_buf+len, &c);
+
+ if (!cursor_pull_b16(&c, &decoded_len)) {
+ fprintf(stderr, "unpad: couldn't pull decoded len\n");
+ return 0;
+ }
+
+ decoded_end_len = decoded_len + 2;
+ decoded_end = padded_buf + decoded_end_len;
+
+ if (decoded_end > c.end) {
+ fprintf(stderr, "decode debug: '%.*s'\n", (int)len-2, (const char *)(padded_buf + 2));
+ fprintf(stderr, "unpad: decoded end (%d) is larger then original buf (%ld)\n",
+ decoded_end_len, len);
+ return 0;
+ }
+
+ c.end = decoded_end;
+
+ *unpadded_len = (uint16_t)cursor_remaining_capacity(&c);
+
+ if (*unpadded_len != decoded_len) {
+ fprintf(stderr, "unpadded_len(%d) != decoded_len(%d)\n",
+ *unpadded_len, decoded_len);
+ return 0;
+ }
+
+ if (decoded_len == 0 || len != (2 + calc_padded_len(decoded_len))) {
+ fprintf(stderr, "padding size is wrong\n");
+ return 0;
+ }
+
+ return 1;
+}
+
+const char *nip44_err_msg(enum ndb_decrypt_result res)
+{
+ switch (res) {
+ case NIP44_OK:
+ return "ok";
+ case NIP44_ERR_FILL_RANDOM_FAILED:
+ return "fill random failed";
+ case NIP44_ERR_INVALID_MAC:
+ return "invalid mac";
+ case NIP44_ERR_SECKEY_VERIFY_FAILED:
+ return "seckey verify failed";
+ case NIP44_ERR_PUBKEY_PARSE_FAILED:
+ return "pubkey parse failed";
+ case NIP44_ERR_ECDH_FAILED:
+ return "ecdh failed";
+ case NIP44_ERR_INVALID_PAYLOAD:
+ return "invalid payload";
+ case NIP44_ERR_UNSUPPORTED_ENCODING:
+ return "unsupported encoding";
+ case NIP44_ERR_BASE64_DECODE:
+ return "error during base64 decoding";
+ case NIP44_ERR_INVALID_PADDING:
+ return "invalid padding";
+ case NIP44_ERR_BUFFER_TOO_SMALL:
+ return "buffer too small";
+ }
+
+ return "unknown";
+}
+
+/* ### Decryption
+ * Before decryption, the event's pubkey and signature MUST be validated as
+ * defined in NIP 01. The public key MUST be a valid non-zero secp256k1 curve
+ * point, and the signature must be valid secp256k1 schnorr signature. For exact
+ * validation rules, refer to BIP-340.
+ */
+enum ndb_decrypt_result
+nip44_decrypt_raw(void *secp,
+ const unsigned char *sender_pubkey,
+ const unsigned char *receiver_seckey,
+ struct nip44_payload *decoded,
+ unsigned char **decrypted, uint16_t *decrypted_len)
+{
+ struct hmac_sha256 conversation_key;
+ struct hmac_sha256 calculated_mac;
+ enum ndb_decrypt_result rc;
+ unsigned char shared_secret[32];
+ struct message_keys keys;
+ secp256k1_context *context = (secp256k1_context *)secp;
+
+ /*
+ 3. Calculate a conversation key
+ - Execute ECDH (scalar multiplication) of public key B by private
+ key A Output `shared_x` must be unhashed, 32-byte encoded x
+ coordinate of the shared point
+ - Use HKDF-extract with sha256, `IKM=shared_x` and
+ `salt=utf8_encode('nip44-v2')`
+ - HKDF output will be a `conversation_key` between two users.
+ */
+ if ((rc = calculate_shared_secret(context, receiver_seckey,
+ sender_pubkey, shared_secret))) {
+ return rc;
+ }
+
+ hmac_sha256(&conversation_key, "nip44-v2", 8, shared_secret, 32);
+
+ /*
+ 5. Calculate message keys
+ - The keys are generated from `conversation_key` and `nonce`.
+ Validate that both are 32 bytes long
+ - Use HKDF-expand, with sha256, `PRK=conversation_key`,
+ `info=nonce` and `L=76`
+ - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32),
+ `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76)
+ */
+ assert(sizeof(keys) == 76);
+ assert(sizeof(conversation_key) == 32);
+
+ hkdf_expand(&keys, sizeof(keys),
+ &conversation_key, sizeof(conversation_key),
+ decoded->nonce, 32);
+
+ /*
+ 6. Calculate MAC (message authentication code) with AAD and compare
+ - Stop and throw an error if MAC doesn't match the decoded one from
+ step 2
+ - Use constant-time comparison algorithm
+ */
+ hmac_aad(&calculated_mac, keys.auth, decoded->nonce,
+ decoded->ciphertext, decoded->ciphertext_len);
+
+ /* TODO(jb55): spec says this needs to be constant time memcmp,
+ * not sure why?
+ */
+ if (memcmp(calculated_mac.sha.u.u8, decoded->mac, 32)) {
+ return NIP44_ERR_INVALID_MAC;
+ }
+
+
+ /*
+ 6. Decrypt ciphertext
+ - Use ChaCha20 with key and nonce from step 3
+ */
+ crypto_stream_chacha20_ietf_xor_ic(decoded->ciphertext,
+ decoded->ciphertext,
+ decoded->ciphertext_len,
+ keys.nonce, 0, keys.key);
+
+ /*
+ 7. Remove padding
+ */
+ if (!unpad(decoded->ciphertext, decoded->ciphertext_len, decrypted_len)) {
+ return NIP44_ERR_INVALID_PADDING;
+ }
+
+ *decrypted = decoded->ciphertext + 2;
+
+ return NIP44_OK;
+}
+
+enum ndb_decrypt_result
+nip44_decrypt(void *secp,
+ const unsigned char *sender_pubkey,
+ const unsigned char *receiver_seckey,
+ const char *payload, int payload_len,
+ unsigned char *buf, size_t bufsize,
+ unsigned char **decrypted, uint16_t *decrypted_len)
+{
+ struct nip44_payload decoded;
+ enum ndb_decrypt_result rc;
+
+ /* decode payload! */
+ if ((rc = nip44_decode_payload(&decoded, buf, bufsize,
+ payload, payload_len))) {
+ return rc;
+ }
+
+ return nip44_decrypt_raw(secp, sender_pubkey, receiver_seckey,
+ &decoded, decrypted, decrypted_len);
+}
+
+/* Encryption */
+enum ndb_decrypt_result
+nip44_encrypt(void *secp, const unsigned char *sender_seckey,
+ const unsigned char *receiver_pubkey,
+ const unsigned char *plaintext, uint16_t plaintext_size,
+ unsigned char *buf, size_t bufsize,
+ char **out, ssize_t *out_len)
+{
+ int rc;
+ struct cursor cursor;
+ struct hmac_sha256 auth, conversation_key;
+ unsigned char shared_secret[32];
+ unsigned char nonce[32];
+ unsigned char *ciphertext;
+ struct message_keys keys;
+ uint16_t ciphertext_len;
+
+ make_cursor(buf, buf+bufsize, &cursor);
+
+ /*
+ 1. Calculate a conversation key
+ - Execute ECDH (scalar multiplication) of public key B by private
+ key A Output `shared_x` must be unhashed, 32-byte encoded x
+ coordinate of the shared point
+
+ - Use HKDF-extract with sha256, `IKM=shared_x` and
+ `salt=utf8_encode('nip44-v2')`
+
+ - HKDF output will be a `conversation_key` between two users.
+
+ - It is always the same, when key roles are swapped:
+ `conv(a, B) == conv(b, A)`
+ */
+ if ((rc = calculate_shared_secret(secp, sender_seckey,
+ receiver_pubkey, shared_secret))) {
+ return rc;
+ }
+
+ hmac_sha256(&conversation_key, "nip44-v2", 8, shared_secret, 32);
+ /*
+ 2. Generate a random 32-byte nonce
+ - Always use CSPRNG
+ - Don't generate a nonce from message content
+ - Don't re-use the same nonce between messages: doing so would make
+ them decryptable, but won't leak the long-term key
+ */
+ if (!fill_random(nonce, sizeof(nonce))) {
+ return NIP44_ERR_FILL_RANDOM_FAILED;
+ }
+
+ /*
+ 3. Calculate message keys
+ - The keys are generated from `conversation_key` and `nonce`.
+ Validate that both are 32 bytes long
+ - Use HKDF-expand, with sha256, `PRK=conversation_key`, `info=nonce`
+ and `L=76`
+ - Slice 76-byte HKDF output into: `chacha_key` (bytes 0..32),
+ `chacha_nonce` (bytes 32..44), `hmac_key` (bytes 44..76)
+ */
+ hkdf_expand(&keys, sizeof(keys),
+ &conversation_key, sizeof(conversation_key),
+ nonce, 32);
+
+ /*
+ 4. Add padding
+ - Content must be encoded from UTF-8 into byte array
+ - Validate plaintext length. Minimum is 1 byte, maximum is 65535 bytes
+ - Padding format is: `[plaintext_length:u16][plaintext][zero_bytes]`
+ - Padding algorithm is related to powers-of-two, with min padded msg
+ size of 32 bytes
+ - Plaintext length is encoded in big-endian as first 2 bytes of the
+ padded blob
+ */
+ if (!cursor_push_byte(&cursor, 0x02))
+ return NIP44_ERR_BUFFER_TOO_SMALL;
+ if (!cursor_push(&cursor, nonce, 32))
+ return NIP44_ERR_BUFFER_TOO_SMALL;
+
+ ciphertext = cursor.p;
+
+ if (!cursor_push_b16(&cursor, plaintext_size))
+ return NIP44_ERR_BUFFER_TOO_SMALL;
+ if (!cursor_push(&cursor, (unsigned char*)plaintext, plaintext_size))
+ return NIP44_ERR_BUFFER_TOO_SMALL;
+ if (!cursor_memset(&cursor, 0, calc_padded_len(plaintext_size) - plaintext_size))
+ return NIP44_ERR_BUFFER_TOO_SMALL;
+
+ ciphertext_len = cursor.p - ciphertext;
+ assert(ciphertext_len >= 132);
+ /*
+ 5. Encrypt padded content
+ - Use ChaCha20, with key and nonce from step 3
+ */
+ crypto_stream_chacha20_ietf_xor_ic(ciphertext, ciphertext,
+ ciphertext_len, keys.nonce, 0,
+ keys.key);
+
+ /*
+ 6. Calculate MAC (message authentication code)
+ - AAD (additional authenticated data) is used - instead of
+ calculating MAC on ciphertext, it's calculated over a concatenation
+ of `nonce` and `ciphertext`
+
+ - Validate that AAD (nonce) is 32 bytes
+ */
+ hmac_aad(&auth, keys.auth, nonce, ciphertext, ciphertext_len);
+
+ if (!cursor_push(&cursor, auth.sha.u.u8, 32))
+ return NIP44_ERR_BUFFER_TOO_SMALL;
+
+ /*
+ 7. Base64-encode (with padding) params using `concat(version, nonce,
+ ciphertext, mac)`
+ */
+ *out = (char*)cursor.p;
+ *out_len = base64_encode((char*)cursor.p,
+ cursor_remaining_capacity(&cursor),
+ (const char*)cursor.start,
+ cursor.p - cursor.start);
+
+ if (*out_len == -1)
+ return NIP44_ERR_BUFFER_TOO_SMALL;
+ return NIP44_OK;
+}
+
diff --git a/nip44.h b/nip44.h
@@ -0,0 +1,57 @@
+
+#ifndef NDB_NIP44_H
+#define NDB_NIP44_H
+
+enum ndb_decrypt_result
+{
+ NIP44_OK = 0,
+ NIP44_ERR_UNSUPPORTED_ENCODING = 1,
+ NIP44_ERR_INVALID_PAYLOAD = 2,
+ NIP44_ERR_BASE64_DECODE = 3,
+ NIP44_ERR_SECKEY_VERIFY_FAILED = 4,
+ NIP44_ERR_PUBKEY_PARSE_FAILED = 5,
+ NIP44_ERR_ECDH_FAILED = 6,
+ NIP44_ERR_FILL_RANDOM_FAILED = 7,
+ NIP44_ERR_INVALID_MAC = 8,
+ NIP44_ERR_INVALID_PADDING = 9,
+ NIP44_ERR_BUFFER_TOO_SMALL = 10,
+};
+
+struct nip44_payload {
+ unsigned char version;
+ unsigned char *nonce;
+ unsigned char *ciphertext;
+ size_t ciphertext_len;
+ unsigned char *mac;
+};
+
+enum ndb_decrypt_result
+nip44_decrypt(void *secp_context,
+ const unsigned char *sender_pubkey,
+ const unsigned char *receiver_seckey,
+ const char *payload, int payload_len,
+ unsigned char *buf, size_t bufsize,
+ unsigned char **decrypted, uint16_t *decrypted_len);
+
+enum ndb_decrypt_result
+nip44_encrypt(void *secp, const unsigned char *sender_seckey,
+ const unsigned char *receiver_pubkey,
+ const unsigned char *plaintext, uint16_t plaintext_size,
+ unsigned char *buf, size_t bufsize,
+ char **out, ssize_t *out_len);
+
+enum ndb_decrypt_result
+nip44_decrypt_raw(void *secp,
+ const unsigned char *sender_pubkey,
+ const unsigned char *receiver_seckey,
+ struct nip44_payload *decoded,
+ unsigned char **decrypted, uint16_t *decrypted_len);
+
+enum ndb_decrypt_result
+nip44_decode_payload(struct nip44_payload *decoded,
+ unsigned char *buf, size_t bufsize,
+ const char *payload, size_t payload_len);
+
+const char *nip44_err_msg(enum ndb_decrypt_result res);
+
+#endif /* NDB_METADATA_H */
diff --git a/nostril.c b/nostril.c
@@ -13,6 +13,7 @@
#include "cursor.h"
#include "hex.h"
+#include "nip44.h"
#include "base64.h"
#include "aes.h"
#include "sha256.h"
@@ -30,6 +31,7 @@
#define HAS_ENCRYPT (1<<4)
#define HAS_DIFFICULTY (1<<5)
#define HAS_MINE_PUBKEY (1<<6)
+#define HAS_GIFTWRAP (1<<7)
struct key {
secp256k1_keypair pair;
@@ -79,6 +81,7 @@ void usage()
printf("\n");
printf(" --content <string> the content of the note\n");
printf(" --dm <hex pubkey> make an encrypted dm to said pubkey. sets kind and tags.\n");
+ printf(" --giftwrap-to <hex pubkey> make an encrypted giftwrap to said pubkey.\n");
printf(" --envelope wrap in [\"EVENT\",...] for easy relaying\n");
printf(" --kind <number> set kind\n");
printf(" --created-at <unix timestamp> set a specific created-at time\n");
@@ -313,13 +316,12 @@ static int init_secp_context(secp256k1_context **ctx)
return secp256k1_context_randomize(*ctx, randomize);
}
-static int generate_event_id(struct nostr_event *ev)
+static int generate_event_id(struct nostr_event *ev,
+ unsigned char *buf, size_t bufsize)
{
- static unsigned char buf[102400];
-
int len;
- if (!(len = event_commitment(ev, buf, sizeof(buf)))) {
+ if (!(len = event_commitment(ev, buf, bufsize))) {
fprintf(stderr, "event_commitment: buffer out of space\n");
return 0;
}
@@ -339,45 +341,65 @@ static int sign_event(secp256k1_context *ctx, struct key *key, struct nostr_even
return 1;
}
-static int print_event(struct nostr_event *ev, int envelope)
+static int all_zeros(unsigned char *buf, size_t bufsize)
{
- unsigned char buf[102400];
- char pubkey[65];
- char id[65];
- char sig[129];
- struct cursor cur;
- int ok;
+ int i;
- ok = hex_encode(ev->id, sizeof(ev->id), id, sizeof(id)) &&
- hex_encode(ev->pubkey, sizeof(ev->pubkey), pubkey, sizeof(pubkey)) &&
- hex_encode(ev->sig, sizeof(ev->sig), sig, sizeof(sig));
+ for (i = 0; i < bufsize; i++) {
+ if (buf[i] != 0)
+ return 0;
+ }
- assert(ok);
+ return 1;
+}
- make_cursor(buf, buf+sizeof(buf), &cur);
- if (!cursor_push_tags(&cur, ev))
- return 0;
+static int event_to_json(struct cursor *c, struct nostr_event *ev, int envelope)
+{
+ char shortbuf[128];
- if (envelope)
- printf("[\"EVENT\",");
+ if (envelope) {
+ if (!cursor_push_str(c, "[\"EVENT\","))
+ return 0;
+ }
- printf("{\"id\": \"%s\",", id);
- printf("\"pubkey\": \"%s\",", pubkey);
- printf("\"created_at\": %" PRIu64 ",", ev->created_at);
- printf("\"kind\": %d,", ev->kind);
- printf("\"tags\": %.*s,", (int)cursor_len(&cur), cur.start);
+ if (!cursor_push_str(c, "{\"id\":\"")) return 0;
+ if (!cursor_push_hex(c, ev->id, 32)) return 0;
+ if (!cursor_push_str(c, "\",")) return 0;
+ if (!cursor_push_str(c, "\"pubkey\":\"")) return 0;
+ if (!cursor_push_hex(c, ev->pubkey, 32)) return 0;
+ if (!cursor_push_str(c, "\",")) return 0;
+ sprintf(shortbuf, "\"created_at\":%" PRIu64 ",", ev->created_at);
+ if (!cursor_push_str(c, shortbuf)) return 0;
+ sprintf(shortbuf, "\"kind\":%d,", ev->kind);
+ if (!cursor_push_str(c, shortbuf)) return 0;
+ if (!cursor_push_str(c, "\"tags\":")) return 0;
+ if (!cursor_push_tags(c, ev)) return 0;
+ if (!cursor_push_str(c, ",\"content\":")) return 0;
+ if (!cursor_push_jsonstr(c, ev->content)) return 0;
+
+ if (!all_zeros(ev->sig, 64)) {
+ if (!cursor_push_str(c, ",\"sig\":\"")) return 0;
+ if (!cursor_push_hex(c, ev->sig, 64)) return 0;
+ if (!cursor_push_byte(c, '"')) return 0;
+ }
- reset_cursor(&cur);
- if (!cursor_push_jsonstr(&cur, ev->content))
- return 0;
+ if (envelope) {
+ if (!cursor_push_str(c, "]")) return 0;
+ }
+
+ return cursor_push_c_str(c, "}");
+}
- printf("\"content\": %.*s,", (int)cursor_len(&cur), cur.start);
- printf("\"sig\": \"%s\"}", sig);
+static int print_event(struct nostr_event *ev, int envelope,
+ unsigned char *buf, size_t bufsize)
+{
+ struct cursor cur;
+ make_cursor(buf, buf+bufsize, &cur);
- if (envelope)
- printf("]");
+ if (!event_to_json(&cur, ev, envelope))
+ return 0;
- printf("\n");
+ printf("%s\n", buf);
return 1;
}
@@ -584,6 +606,13 @@ static int parse_args(int argc, const char *argv[], struct args *args, struct no
} else if (!strcmp(arg, "--content")) {
arg = *argv++; argc--;
args->content = arg;
+ } else if (!strcmp(arg, "--giftwrap-to")) {
+ arg = *argv++; argc--;
+ if (!hex_decode(arg, strlen(arg), args->encrypt_to, 32)) {
+ fprintf(stderr, "could not decode giftwrap-to pubkey");
+ return 0;
+ }
+ args->flags |= HAS_GIFTWRAP;
} else {
fprintf(stderr, "unexpected argument '%s'\n", arg);
return 0;
@@ -652,7 +681,8 @@ static int ensure_nonce_tag(struct nostr_event *ev, int target, int *index)
return nostr_add_tag_n(ev, ts, 3);
}
-static int mine_event(struct nostr_event *ev, int difficulty)
+static int mine_event(struct nostr_event *ev, int difficulty,
+ unsigned char *buf, size_t bufsize)
{
char *strnonce = malloc(33);
struct nostr_tag *tag;
@@ -670,7 +700,7 @@ static int mine_event(struct nostr_event *ev, int difficulty)
for (nonce = 0;; nonce++) {
snprintf(strnonce, 32, "%" PRIu64, nonce);
- if (!generate_event_id(ev))
+ if (!generate_event_id(ev, buf, bufsize))
return 0;
if ((res = count_leading_zero_bits(ev->id)) >= difficulty) {
@@ -682,6 +712,121 @@ static int mine_event(struct nostr_event *ev, int difficulty)
return 0;
}
+static int make_giftwrap(secp256k1_context *ctx, struct key *key,
+ struct nostr_event *rumor, unsigned char receiver_pubkey[32],
+ unsigned char *buf, size_t bufsize,
+ int is_envelope)
+{
+ struct nostr_event giftwrap = {0};
+ struct nostr_event seal = {0};
+ struct key wrap_key;
+ char *b64, *json;
+ struct cursor arena, *c;
+ ssize_t b64_len;
+ enum ndb_decrypt_result ok;
+
+ c = &arena;
+ make_cursor(buf, buf+bufsize, c);
+ if (!fill_random(wrap_key.secret, sizeof(wrap_key.secret)))
+ return 0;
+ create_key(ctx, &wrap_key); /* giftwrap key */
+
+ memcpy(rumor->pubkey, key->pubkey, 32);
+ if (!generate_event_id(rumor, c->p, cursor_remaining_capacity(c)))
+ return 0;
+
+ /* rumor data */
+ json = (char*)c->p;
+ if (!event_to_json(c, rumor, 0))
+ return 0;
+
+ ok = nip44_encrypt(ctx, key->secret, receiver_pubkey,
+ (unsigned char *)json, c->p - (unsigned char *)json-1,
+ c->p, cursor_remaining_capacity(c),
+ &b64, &b64_len);
+
+ if (ok != NIP44_OK) {
+ fprintf(stderr, "rumor nip44 encrypt failed: %s\n",
+ nip44_err_msg(ok));
+ return 0;
+ }
+ assert(b64_len % 4 == 0);
+ c->p = b64 + b64_len;
+ if (!cursor_push_byte(c, 0))
+ return 0;
+
+ fprintf(stderr, "rumor %s\n\n", json);
+
+ /* seal data */
+ seal.kind = 13;
+ seal.created_at = rumor->created_at;
+ seal.content = b64;
+ assert(strlen(b64) % 4 == 0);
+
+ memcpy(seal.pubkey, key->pubkey, 32);
+ if (!generate_event_id(&seal, c->p, cursor_remaining_capacity(c)))
+ return 0;
+ if (!sign_event(ctx, key, &seal))
+ return 0;
+
+ json = (char*)c->p;
+ if (!event_to_json(c, &seal, 0)) {
+ fprintf(stderr, "seal -> json failed, not enough space?\n");
+ return 0;
+ }
+ fprintf(stderr, "seal %.*s\n\n", (int)(c->p - (unsigned char*)json), json);
+
+ /* encrypt seal for giftwrap contents */
+ ok = nip44_encrypt(ctx, wrap_key.secret, receiver_pubkey,
+ (unsigned char *)json, c->p - (unsigned char *)json - 1,
+ c->p, cursor_remaining_capacity(c),
+ &b64, &b64_len);
+
+ if (ok != NIP44_OK) {
+ fprintf(stderr, "seal nip44 encrypt failed: %s\n",
+ nip44_err_msg(ok));
+ return 0;
+ }
+
+ assert(b64_len % 4 == 0);
+ c->p = b64 + b64_len;
+ if (!cursor_push_byte(c, 0))
+ return 0;
+
+ giftwrap.content = b64;
+
+ /* giftwrap data */
+ json = (char*)c->p;
+ if (!cursor_push_hex(c, wrap_key.secret, 32)) {
+ fprintf(stderr, "buffer too small, bug jb55 to increase\n");
+ return 0;
+ }
+ fprintf(stderr, "giftwrap_sec %.*s\n\n", 64, json);
+
+ giftwrap.created_at = rumor->created_at;
+ giftwrap.kind = 1059;
+ memcpy(giftwrap.pubkey, wrap_key.pubkey, 32);
+
+ json = (char*)c->p;
+ cursor_push_hex(c, receiver_pubkey, 32);
+ cursor_push_byte(c, 0);
+ nostr_add_tag(&giftwrap, "p", json);
+
+ if (!generate_event_id(&giftwrap, c->p, cursor_remaining_capacity(c)))
+ return 0;
+
+ json = (char*)c->p;
+ if (!event_to_json(c, &giftwrap, is_envelope)) {
+ fprintf(stderr, "buffer too small, bug jb55 to increase\n");
+ return 0;
+ }
+
+ printf("%s\n", json);
+
+ free(buf);
+ return 1;
+}
+
static int make_encrypted_dm(secp256k1_context *ctx, struct key *key,
struct nostr_event *ev, unsigned char nostr_pubkey[32], int kind)
{
@@ -707,7 +852,8 @@ static int make_encrypted_dm(secp256k1_context *ctx, struct key *key,
return 0;
}
- if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey, sizeof(compressed_pubkey))) {
+ if (!secp256k1_ec_pubkey_parse(ctx, &pubkey, compressed_pubkey,
+ sizeof(compressed_pubkey))) {
fprintf(stderr, "make_encrypted_dm: ec_pubkey_parse failed\n");
return 0;
}
@@ -787,6 +933,8 @@ int main(int argc, const char *argv[])
struct args args = {0};
struct nostr_event ev = {0};
struct key key;
+ unsigned char *buf;
+ size_t bufsize;
secp256k1_context *ctx;
if (argc < 2)
@@ -827,24 +975,35 @@ int main(int argc, const char *argv[])
fprintf(stderr, "\n");
}
+ /* 8 MiB */
+ bufsize = 2 << 22;
+ buf = malloc(bufsize);
+
if (args.flags & HAS_ENCRYPT) {
int kind = args.flags & HAS_KIND? args.kind : 4;
if (!make_encrypted_dm(ctx, &key, &ev, args.encrypt_to, kind)) {
fprintf(stderr, "error making encrypted dm\n");
- return 0;
+ return 1;
}
+ } else if (args.flags & HAS_GIFTWRAP) {
+ if (!make_giftwrap(ctx, &key, &ev, args.encrypt_to,
+ buf, bufsize, args.flags & HAS_ENVELOPE)) {
+ fprintf(stderr, "error making encrypted dm\n");
+ return 1;
+ }
+ return 0;
}
// set the event's pubkey
memcpy(ev.pubkey, key.pubkey, 32);
if (args.flags & HAS_DIFFICULTY && !(args.flags & HAS_MINE_PUBKEY)) {
- if (!mine_event(&ev, args.difficulty)) {
+ if (!mine_event(&ev, args.difficulty, buf, bufsize)) {
fprintf(stderr, "error when mining id\n");
return 22;
}
} else {
- if (!generate_event_id(&ev)) {
+ if (!generate_event_id(&ev, buf, bufsize)) {
fprintf(stderr, "could not generate event id\n");
return 5;
}
@@ -855,11 +1014,13 @@ int main(int argc, const char *argv[])
return 6;
}
- if (!print_event(&ev, args.flags & HAS_ENVELOPE)) {
+ if (!print_event(&ev, args.flags & HAS_ENVELOPE, buf, bufsize)) {
fprintf(stderr, "buffer too small\n");
return 88;
}
+ free(buf);
+
return 0;
}
diff --git a/shell.nix b/shell.nix
@@ -2,4 +2,5 @@
with pkgs;
mkShell {
buildInputs = [ scdoc ];
+ nativeBuildInputs = [ autoreconfHook ];
}