commit ba5e344f0adc2dd9cce774a970d64b27176b0b47
parent f9791a1d58272c921861c08a361a0006a00b82bc
Author: William Casarin <jb55@jb55.com>
Date: Sun, 16 Jan 2022 07:42:34 -0800
initial test program, make more lib friendly
Diffstat:
M | .gitignore | | | 2 | +- |
M | Makefile | | | 11 | ++++++----- |
A | cursor.h | | | 398 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | error.c | | | 49 | +++++++++++++++++++++++++++++++++++++++++++++++++ |
A | error.h | | | 32 | ++++++++++++++++++++++++++++++++ |
A | handshake.c | | | 433 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
M | handshake.h | | | 14 | +++++++++++--- |
M | lnsocket.c | | | 523 | ++++++++++--------------------------------------------------------------------- |
A | lnsocket.h | | | 12 | ++++++++++++ |
A | lnsocket_internal.h | | | 11 | +++++++++++ |
A | test.c | | | 22 | ++++++++++++++++++++++ |
A | typedefs.h | | | 13 | +++++++++++++ |
D | types.h | | | 8 | -------- |
A | varint.c | | | 75 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | varint.h | | | 14 | ++++++++++++++ |
15 files changed, 1139 insertions(+), 478 deletions(-)
diff --git a/.gitignore b/.gitignore
@@ -4,4 +4,4 @@
/tags
*.o
/configurator
-/lnsocket
+/test
diff --git a/Makefile b/Makefile
@@ -3,10 +3,10 @@ CFLAGS=-Wall -g -Og -Ideps/secp256k1/include -Ideps/libsodium/src/libsodium/incl
LDFLAGS=
ARS=deps/secp256k1/.libs/libsecp256k1.a deps/libsodium/src/libsodium/.libs/libsodium.a
-OBJS=sha256.o hkdf.o hmac.o sha512.o lnsocket.o
+OBJS=sha256.o hkdf.o hmac.o sha512.o lnsocket.o error.o handshake.o
DEPS=$(OBJS) config.h
-all: lnsocket
+all: test
config.h: configurator
./configurator > $@
@@ -37,14 +37,15 @@ deps/libsodium/src/libsodium/.libs/libsodium.a: deps/libsodium/configure
cd deps/libsodium/src/libsodium; \
make -j2 libsodium.la
-lnsocket: $(DEPS) $(ARS)
- $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
+test: test.o $(DEPS) $(ARS)
+ @echo "ld test"
+ @$(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@
tags: fake
find . -name '*.c' -or -name '*.h' | xargs ctags
clean: fake
- rm -f lnsocket $(DEPS)
+ rm -f test lnsocket $(DEPS)
deepclean: clean
rm -f $(ARS) deps/secp256k1/src/libsecp256k1-config.h
diff --git a/cursor.h b/cursor.h
@@ -0,0 +1,398 @@
+
+
+#ifndef PROTOVERSE_CURSOR_H
+#define PROTOVERSE_CURSOR_H
+
+#include "typedefs.h"
+#include "varint.h"
+
+#include <stdio.h>
+#include <assert.h>
+#include <string.h>
+
+#define unlikely(x) __builtin_expect((x),0)
+#define likely(x) __builtin_expect((x),1)
+
+struct cursor {
+ unsigned char *start;
+ unsigned char *p;
+ unsigned char *end;
+};
+
+struct array {
+ struct cursor cur;
+ unsigned int elem_size;
+};
+
+static inline void reset_cursor(struct cursor *cursor)
+{
+ cursor->p = cursor->start;
+}
+
+static inline void wipe_cursor(struct cursor *cursor)
+{
+ reset_cursor(cursor);
+ memset(cursor->start, 0, cursor->end - cursor->start);
+}
+
+static inline void make_cursor(u8 *start, u8 *end, struct cursor *cursor)
+{
+ cursor->start = start;
+ cursor->p = start;
+ cursor->end = end;
+}
+
+static inline void make_array(struct array *a, u8* start, u8 *end, unsigned int elem_size)
+{
+ make_cursor(start, end, &a->cur);
+ a->elem_size = elem_size;
+}
+
+static inline int cursor_eof(struct cursor *c)
+{
+ return c->p == c->end;
+}
+
+static inline void *cursor_malloc(struct cursor *mem, unsigned long size)
+{
+ void *ret;
+
+ if (mem->p + size > mem->end) {
+ return NULL;
+ }
+
+ ret = mem->p;
+ mem->p += size;
+
+ return ret;
+}
+
+static inline void *cursor_alloc(struct cursor *mem, unsigned long size)
+{
+ void *ret;
+ if (!(ret = cursor_malloc(mem, size))) {
+ return 0;
+ }
+
+ memset(ret, 0, size);
+ return ret;
+}
+
+static inline int cursor_slice(struct cursor *mem, struct cursor *slice, size_t size)
+{
+ u8 *p;
+ if (!(p = cursor_alloc(mem, size))) {
+ return 0;
+ }
+ make_cursor(p, mem->p, slice);
+ return 1;
+}
+
+
+static inline void copy_cursor(struct cursor *src, struct cursor *dest)
+{
+ dest->start = src->start;
+ dest->p = src->p;
+ dest->end = src->end;
+}
+
+static inline int pull_byte(struct cursor *cursor, u8 *c)
+{
+ if (unlikely(cursor->p + 1 > cursor->end))
+ return 0;
+
+ *c = *cursor->p;
+ cursor->p++;
+
+ return 1;
+}
+
+static inline int cursor_pull_c_str(struct cursor *cursor, const char **str)
+{
+ *str = (const char*)cursor->p;
+
+ for (; cursor->p < cursor->end; cursor->p++) {
+ if (*cursor->p == 0) {
+ cursor->p++;
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+
+static inline int cursor_push_byte(struct cursor *cursor, u8 c)
+{
+ if (unlikely(cursor->p + 1 > cursor->end)) {
+ return 0;
+ }
+
+ *cursor->p = c;
+ cursor->p++;
+
+ return 1;
+}
+
+static inline int cursor_pull(struct cursor *cursor, u8 *data, int len)
+{
+ if (unlikely(cursor->p + len > cursor->end)) {
+ return 0;
+ }
+
+ memcpy(data, cursor->p, len);
+ cursor->p += len;
+
+ return 1;
+}
+
+static inline int pull_data_into_cursor(struct cursor *cursor,
+ struct cursor *dest,
+ unsigned char **data,
+ int len)
+{
+ int ok;
+
+ if (unlikely(dest->p + len > dest->end)) {
+ printf("not enough room in dest buffer\n");
+ return 0;
+ }
+
+ ok = cursor_pull(cursor, dest->p, len);
+ if (!ok) return 0;
+
+ *data = dest->p;
+ dest->p += len;
+
+ return 1;
+}
+
+static inline int cursor_dropn(struct cursor *cur, int size, int n)
+{
+ if (n == 0)
+ return 1;
+
+ if (unlikely(cur->p - size*n < cur->start)) {
+ return 0;
+ }
+
+ cur->p -= size*n;
+ return 1;
+}
+
+static inline int cursor_drop(struct cursor *cur, int size)
+{
+ return cursor_dropn(cur, size, 1);
+}
+
+static inline unsigned char *cursor_topn(struct cursor *cur, int len, int n)
+{
+ n += 1;
+ if (unlikely(cur->p - len*n < cur->start)) {
+ return NULL;
+ }
+ return cur->p - len*n;
+}
+
+static inline unsigned char *cursor_top(struct cursor *cur, int len)
+{
+ if (unlikely(cur->p - len < cur->start)) {
+ return NULL;
+ }
+ return cur->p - len;
+}
+
+static inline int cursor_top_int(struct cursor *cur, int *i)
+{
+ u8 *p;
+ if (unlikely(!(p = cursor_top(cur, sizeof(*i))))) {
+ return 0;
+ }
+ *i = *((int*)p);
+ return 1;
+}
+
+static inline int cursor_pop(struct cursor *cur, u8 *data, int len)
+{
+ if (unlikely(cur->p - len < cur->start)) {
+ return 0;
+ }
+
+ cur->p -= len;
+ memcpy(data, cur->p, len);
+
+ return 1;
+}
+
+static inline int cursor_push(struct cursor *cursor, u8 *data, int len)
+{
+ if (unlikely(cursor->p + len >= cursor->end)) {
+ return 0;
+ }
+
+ if (cursor->p != data)
+ memcpy(cursor->p, data, len);
+
+ cursor->p += len;
+
+ return 1;
+}
+
+static inline int cursor_push_int(struct cursor *cursor, int i)
+{
+ return cursor_push(cursor, (u8*)&i, sizeof(i));
+}
+
+static inline size_t cursor_count(struct cursor *cursor, size_t elem_size)
+{
+ return (cursor->p - cursor->start)/elem_size;
+}
+
+/* TODO: push_varint */
+static inline int push_varint(struct cursor *cursor, int n)
+{
+ int ok, len;
+ unsigned char b;
+ len = 0;
+
+ while (1) {
+ b = (n & 0xFF) | 0x80;
+ n >>= 7;
+ if (n == 0) {
+ b &= 0x7F;
+ ok = cursor_push_byte(cursor, b);
+ len++;
+ if (!ok) return 0;
+ break;
+ }
+
+ ok = cursor_push_byte(cursor, b);
+ len++;
+ if (!ok) return 0;
+ }
+
+ return len;
+}
+
+/* TODO: pull_varint */
+static inline int pull_varint(struct cursor *cursor, int *n)
+{
+ int ok, i;
+ unsigned char b;
+ *n = 0;
+
+ for (i = 0;; i++) {
+ ok = pull_byte(cursor, &b);
+ if (!ok) return 0;
+
+ *n |= ((int)b & 0x7F) << (i * 7);
+
+ /* is_last */
+ if ((b & 0x80) == 0) {
+ return i+1;
+ }
+
+ if (i == 4) return 0;
+ }
+
+ return 0;
+}
+
+static inline int cursor_pull_int(struct cursor *cursor, int *i)
+{
+ return cursor_pull(cursor, (u8*)i, sizeof(*i));
+}
+
+static inline int cursor_push_u16(struct cursor *cursor, u16 i)
+{
+ return cursor_push(cursor, (u8*)&i, sizeof(i));
+}
+
+static inline void *index_cursor(struct cursor *cursor, unsigned int index, int elem_size)
+{
+ u8 *p;
+ p = &cursor->start[elem_size * index];
+
+ if (unlikely(p >= cursor->end))
+ return NULL;
+
+ return (void*)p;
+}
+
+
+static inline int push_sized_str(struct cursor *cursor, const char *str, int len)
+{
+ return cursor_push(cursor, (u8*)str, len);
+}
+
+static inline int cursor_push_str(struct cursor *cursor, const char *str)
+{
+ return cursor_push(cursor, (u8*)str, strlen(str));
+}
+
+static inline int cursor_push_c_str(struct cursor *cursor, const char *str)
+{
+ return cursor_push_str(cursor, str) && cursor_push_byte(cursor, 0);
+}
+
+/* TODO: push varint size */
+static inline int push_prefixed_str(struct cursor *cursor, const char *str)
+{
+ int ok, len;
+ len = strlen(str);
+ ok = push_varint(cursor, len);
+ if (!ok) return 0;
+ return push_sized_str(cursor, str, len);
+}
+
+static inline int pull_prefixed_str(struct cursor *cursor, struct cursor *dest_buf, const char **str)
+{
+ int len, ok;
+
+ ok = pull_varint(cursor, &len);
+ if (!ok) return 0;
+
+ if (unlikely(dest_buf->p + len > dest_buf->end)) {
+ return 0;
+ }
+
+ ok = pull_data_into_cursor(cursor, dest_buf, (unsigned char**)str, len);
+ if (!ok) return 0;
+
+ ok = cursor_push_byte(dest_buf, 0);
+
+ return 1;
+}
+
+static inline int cursor_remaining_capacity(struct cursor *cursor)
+{
+ return cursor->end - cursor->p;
+}
+
+
+#define max(a,b) ((a) > (b) ? (a) : (b))
+static inline void cursor_print_around(struct cursor *cur, int range)
+{
+ unsigned char *c;
+
+ printf("[%ld/%ld]\n", cur->p - cur->start, cur->end - cur->start);
+
+ c = max(cur->p - range, cur->start);
+ for (; c < cur->end && c < (cur->p + range); c++) {
+ printf("%02x", *c);
+ }
+ printf("\n");
+
+ c = max(cur->p - range, cur->start);
+ for (; c < cur->end && c < (cur->p + range); c++) {
+ if (c == cur->p) {
+ printf("^");
+ continue;
+ }
+ printf(" ");
+ }
+ printf("\n");
+}
+#undef max
+
+#endif
diff --git a/error.c b/error.c
@@ -0,0 +1,49 @@
+
+#include "error.h"
+
+#include <stdlib.h>
+#include <stdarg.h>
+
+int note_error_(struct errors *errs_, const char *fmt, ...)
+{
+ static char buf[512];
+ struct error err;
+ struct cursor *errs;
+ va_list ap;
+
+ errs = &errs_->cur;
+
+ if (errs_->enabled == 0)
+ return 0;
+
+ va_start(ap, fmt);
+ vsprintf(buf, fmt, ap);
+ va_end(ap);
+
+ err.msg = buf;
+
+ if (!cursor_push_error(errs, &err)) {
+ fprintf(stderr, "arena OOM when recording error, ");
+ fprintf(stderr, "errs->p at %ld, remaining %ld, strlen %ld\n",
+ errs->p - errs->start, errs->end - errs->p, strlen(buf));
+ }
+
+ return 0;
+}
+
+void print_error_backtrace(struct errors *errors)
+{
+ struct cursor errs;
+ struct error err;
+
+ copy_cursor(&errors->cur, &errs);
+ errs.p = errs.start;
+
+ while (errs.p < errors->cur.p) {
+ if (!cursor_pull_error(&errs, &err)) {
+ printf("backtrace: couldn't pull error\n");
+ return;
+ }
+ printf("%s\n", err.msg);
+ }
+}
diff --git a/error.h b/error.h
@@ -0,0 +1,32 @@
+
+#ifndef LNSOCKET_ERROR_H
+#define LNSOCKET_ERROR_H
+
+#include "cursor.h"
+
+struct error {
+ const char *msg;
+};
+
+struct errors {
+ struct cursor cur;
+ int enabled;
+};
+
+#define note_error(errs, fmt, ...) note_error_(errs, "%s: " fmt, __FUNCTION__, ##__VA_ARGS__)
+
+static inline int cursor_push_error(struct cursor *cur, struct error *err)
+{
+ return cursor_push_c_str(cur, err->msg);
+}
+
+static inline int cursor_pull_error(struct cursor *cur, struct error *err)
+{
+ return cursor_pull_c_str(cur, &err->msg);
+}
+
+int note_error_(struct errors *errs, const char *fmt, ...);
+
+void print_error_backtrace(struct errors *errors);
+
+#endif /* LNSOCKET_ERROR_H */
diff --git a/handshake.c b/handshake.c
@@ -0,0 +1,433 @@
+
+#include "compiler.h"
+#include "endian.h"
+#include "error.h"
+#include "handshake.h"
+#include "hkdf.h"
+#include "lnsocket_internal.h"
+#include "sha256.h"
+#include <errno.h>
+#include <secp256k1_ecdh.h>
+#include <unistd.h>
+#include <sodium/randombytes.h>
+
+struct keypair generate_key(secp256k1_context *ctx)
+{
+ struct keypair k;
+
+ do {
+ randombytes_buf(k.priv.secret.data, sizeof(k.priv.secret.data));
+ } while (!secp256k1_ec_pubkey_create(ctx, &k.pub.pubkey,
+ k.priv.secret.data));
+ return k;
+}
+
+
+/* h = SHA-256(h || data) */
+static void sha_mix_in(struct sha256 *h, const void *data, size_t len)
+{
+ struct sha256_ctx shactx;
+
+ sha256_init(&shactx);
+ sha256_update(&shactx, h->u.u8, sizeof(*h));
+ sha256_update(&shactx, data, len);
+ sha256_done(&shactx, h);
+}
+
+/* h = SHA-256(h || pub.serializeCompressed()) */
+static void sha_mix_in_key(secp256k1_context *ctx, struct sha256 *h,
+ const struct pubkey *key)
+{
+ u8 der[PUBKEY_CMPR_LEN];
+ size_t len = sizeof(der);
+
+ secp256k1_ec_pubkey_serialize(ctx, der, &len, &key->pubkey,
+ SECP256K1_EC_COMPRESSED);
+ assert(len == sizeof(der));
+ sha_mix_in(h, der, sizeof(der));
+}
+
+/* out1, out2 = HKDF(in1, in2)` */
+static void hkdf_two_keys(struct secret *out1, struct secret *out2,
+ const struct secret *in1,
+ const void *in2, size_t in2_size)
+{
+ /* BOLT #8:
+ *
+ * * `HKDF(salt,ikm)`: a function defined in `RFC 5869`<sup>[3](#reference-3)</sup>,
+ * evaluated with a zero-length `info` field
+ * * All invocations of `HKDF` implicitly return 64 bytes
+ * of cryptographic randomness using the extract-and-expand
+ * component of the `HKDF`.
+ */
+ struct secret okm[2];
+
+ hkdf_sha256(okm, sizeof(okm), in1, sizeof(*in1), in2, in2_size,
+ NULL, 0);
+ *out1 = okm[0];
+ *out2 = okm[1];
+}
+
+
+static void le64_nonce(unsigned char *npub, u64 nonce)
+{
+ /* BOLT #8:
+ *
+ * ...with nonce `n` encoded as 32 zero bits, followed by a
+ * *little-endian* 64-bit value. Note: this follows the Noise Protocol
+ * convention, rather than our normal endian
+ */
+ le64 le_nonce = cpu_to_le64(nonce);
+ const size_t zerolen = crypto_aead_chacha20poly1305_ietf_NPUBBYTES - sizeof(le_nonce);
+
+ BUILD_ASSERT(crypto_aead_chacha20poly1305_ietf_NPUBBYTES >= sizeof(le_nonce));
+ /* First part is 0, followed by nonce. */
+ memset(npub, 0, zerolen);
+ memcpy(npub + zerolen, &le_nonce, sizeof(le_nonce));
+}
+
+/* BOLT #8:
+ * * `encryptWithAD(k, n, ad, plaintext)`: outputs `encrypt(k, n, ad,
+ * plaintext)`
+ * * Where `encrypt` is an evaluation of `ChaCha20-Poly1305` (IETF
+ * variant) with the passed arguments, with nonce `n`
+ */
+static void encrypt_ad(const struct secret *k, u64 nonce,
+ const void *additional_data, size_t additional_data_len,
+ const void *plaintext, size_t plaintext_len,
+ void *output, size_t outputlen)
+{
+ unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
+ unsigned long long clen;
+ int ret;
+
+ assert(outputlen == plaintext_len + crypto_aead_chacha20poly1305_ietf_ABYTES);
+ le64_nonce(npub, nonce);
+ BUILD_ASSERT(sizeof(*k) == crypto_aead_chacha20poly1305_ietf_KEYBYTES);
+
+ ret = crypto_aead_chacha20poly1305_ietf_encrypt(
+ output, &clen, memcheck(plaintext, plaintext_len),
+ plaintext_len, additional_data, additional_data_len,
+ NULL, npub, k->data);
+
+ assert(ret == 0);
+ assert(clen == plaintext_len + crypto_aead_chacha20poly1305_ietf_ABYTES);
+}
+
+static inline void check_act_one(const struct act_one *act1)
+{
+ /* BOLT #8:
+ *
+ * : 1 byte for the handshake version, 33 bytes for the compressed
+ * ephemeral public key of the initiator, and 16 bytes for the
+ * `poly1305` tag.
+ */
+ BUILD_ASSERT(sizeof(act1->v) == 1);
+ BUILD_ASSERT(sizeof(act1->pubkey) == 33);
+ BUILD_ASSERT(sizeof(act1->tag) == 16);
+}
+
+static void print_hex(u8 *bytes, int len) {
+ int i;
+ for (i = 0; i < len; ++i) {
+ printf("%02x", bytes[i]);
+ }
+}
+
+void new_handshake(secp256k1_context *secp, struct handshake *handshake,
+ const struct pubkey *responder_id)
+{
+ /* BOLT #8:
+ *
+ * Before the start of Act One, both sides initialize their
+ * per-sessions state as follows:
+ *
+ * 1. `h = SHA-256(protocolName)`
+ * * where `protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"`
+ * encoded as an ASCII string
+ */
+ sha256(&handshake->h, "Noise_XK_secp256k1_ChaChaPoly_SHA256",
+ strlen("Noise_XK_secp256k1_ChaChaPoly_SHA256"));
+
+ /* BOLT #8:
+ *
+ * 2. `ck = h`
+ */
+ BUILD_ASSERT(sizeof(handshake->h) == sizeof(handshake->ck));
+ memcpy(&handshake->ck, &handshake->h, sizeof(handshake->ck));
+
+ /* BOLT #8:
+ *
+ * 3. `h = SHA-256(h || prologue)`
+ * * where `prologue` is the ASCII string: `lightning`
+ */
+ sha_mix_in(&handshake->h, "lightning", strlen("lightning"));
+
+ /* BOLT #8:
+ *
+ * As a concluding step, both sides mix the responder's public key
+ * into the handshake digest:
+ *
+ * * The initiating node mixes in the responding node's static public
+ * key serialized in Bitcoin's compressed format:
+ * * `h = SHA-256(h || rs.pub.serializeCompressed())`
+ *
+ * * The responding node mixes in their local static public key
+ * serialized in Bitcoin's compressed format:
+ * * `h = SHA-256(h || ls.pub.serializeCompressed())`
+ */
+ sha_mix_in_key(secp, &handshake->h, responder_id);
+}
+
+static void print_act_two(struct act_two *two)
+{
+ printf("ACT2 v %d pubkey ", two->v);
+ print_hex(two->pubkey, sizeof(two->pubkey));
+ printf(" tag ");
+ print_hex(two->tag, sizeof(two->tag));
+ printf("\n");
+}
+
+/* BOLT #8:
+ * * `decryptWithAD(k, n, ad, ciphertext)`: outputs `decrypt(k, n, ad,
+ * ciphertext)`
+ * * Where `decrypt` is an evaluation of `ChaCha20-Poly1305` (IETF
+ * variant) with the passed arguments, with nonce `n`
+ */
+static int decrypt(const struct secret *k, u64 nonce,
+ const void *additional_data, size_t additional_data_len,
+ const void *ciphertext, size_t ciphertext_len,
+ void *output, size_t outputlen)
+{
+ unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
+ unsigned long long mlen;
+
+ assert(outputlen == ciphertext_len - crypto_aead_chacha20poly1305_ietf_ABYTES);
+
+ le64_nonce(npub, nonce);
+ BUILD_ASSERT(sizeof(*k) == crypto_aead_chacha20poly1305_ietf_KEYBYTES);
+ if (crypto_aead_chacha20poly1305_ietf_decrypt(output, &mlen, NULL,
+ memcheck(ciphertext, ciphertext_len),
+ ciphertext_len,
+ additional_data, additional_data_len,
+ npub, k->data) != 0) {
+ return 0;
+ }
+
+ assert(mlen == ciphertext_len - crypto_aead_chacha20poly1305_ietf_ABYTES);
+ return 1;
+}
+
+static int act_three_initiator(struct lnsocket *ln, struct handshake *h)
+{
+ u8 spub[PUBKEY_CMPR_LEN];
+ size_t len = sizeof(spub);
+
+ /* BOLT #8:
+ * 1. `c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed())`
+ * * where `s` is the static public key of the initiator
+ */
+ secp256k1_ec_pubkey_serialize(ln->secp, spub, &len,
+ &ln->key.pub.pubkey,
+ SECP256K1_EC_COMPRESSED);
+ encrypt_ad(&h->temp_k, 1, &h->h, sizeof(h->h), spub, sizeof(spub),
+ h->act3.ciphertext, sizeof(h->act3.ciphertext));
+
+ /* BOLT #8:
+ * 2. `h = SHA-256(h || c)`
+ */
+ sha_mix_in(&h->h, h->act3.ciphertext, sizeof(h->act3.ciphertext));
+
+ /* BOLT #8:
+ *
+ * 3. `se = ECDH(s.priv, re)`
+ * * where `re` is the ephemeral public key of the responder
+ */
+ if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->re.pubkey,
+ ln->key.priv.secret.data, NULL, NULL))
+ return note_error(&ln->errs, "act3 ecdh handshake failed");
+
+ /* BOLT #8:
+ *
+ * 4. `ck, temp_k3 = HKDF(ck, se)`
+ * * The final intermediate shared secret is mixed into the running chaining key.
+ */
+ hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss));
+
+ /* BOLT #8:
+ *
+ * 5. `t = encryptWithAD(temp_k3, 0, h, zero)`
+ * * where `zero` is a zero-length plaintext
+ *
+ */
+ encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0,
+ h->act3.tag, sizeof(h->act3.tag));
+
+ /* BOLT #8:
+ *
+ * 8. Send `m = 0 || c || t` over the network buffer.
+ *
+ */
+ h->act3.v = 0;
+
+ if (write(ln->socket, &h->act3, ACT_THREE_SIZE) != ACT_THREE_SIZE) {
+ return note_error(&ln->errs, "handshake failed on initial send");
+ }
+
+ return note_error(&ln->errs, "handshake success!\n");
+}
+
+// act2: read the response to the message sent in act1
+static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
+{
+ /* BOLT #8:
+ *
+ * 1. Read _exactly_ 50 bytes from the network buffer.
+ *
+ * 2. Parse the read message (`m`) into `v`, `re`, and `c`:
+ * * where `v` is the _first_ byte of `m`, `re` is the next 33
+ * bytes of `m`, and `c` is the last 16 bytes of `m`.
+ */
+ ssize_t size;
+
+ if ((size = read(ln->socket, &h->act2, ACT_TWO_SIZE)) != ACT_TWO_SIZE) {
+ printf("read %ld bytes, expected %d\n", size, ACT_TWO_SIZE);
+ return note_error(&ln->errs, "%s", strerror(errno));
+ }
+
+ print_act_two(&h->act2);
+
+ /* BOLT #8:
+ *
+ * 3. If `v` is an unrecognized handshake version, then the responder
+ * MUST abort the connection attempt.
+ */
+ if (h->act2.v != 0)
+ return note_error(&ln->errs, "unrecognized handshake version");
+
+ /* BOLT #8:
+ *
+ * * The raw bytes of the remote party's ephemeral public key
+ * (`re`) are to be deserialized into a point on the curve using
+ * affine coordinates as encoded by the key's serialized
+ * composed format.
+ */
+ if (secp256k1_ec_pubkey_parse(ln->secp, &h->re.pubkey, h->act2.pubkey,
+ sizeof(h->act2.pubkey)) != 1) {
+ return note_error(&ln->errs, "failed to parse remote pubkey");
+ }
+
+ /* BOLT #8:
+ *
+ * 4. `h = SHA-256(h || re.serializeCompressed())`
+ */
+ sha_mix_in_key(ln->secp, &h->h, &h->re);
+
+ /* BOLT #8:
+ *
+ * 5. `es = ECDH(s.priv, re)`
+ */
+ if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->re.pubkey,
+ h->e.priv.secret.data, NULL, NULL)) {
+ return note_error(&ln->errs, "act2 ecdh failed");
+ }
+
+ /* BOLT #8:
+ *
+ * 6. `ck, temp_k2 = HKDF(ck, ee)`
+ * * A new temporary encryption key is generated, which is
+ * used to generate the authenticating MAC.
+ */
+ hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss));
+
+ /* BOLT #8:
+ *
+ * 7. `p = decryptWithAD(temp_k2, 0, h, c)`
+ * * If the MAC check in this operation fails, then the initiator
+ * MUST terminate the connection without any further messages.
+ */
+ if (!decrypt(&h->temp_k, 0, &h->h, sizeof(h->h),
+ h->act2.tag, sizeof(h->act2.tag), NULL, 0)) {
+ return note_error(&ln->errs, "handshake decrypt failed");
+ }
+
+ /* BOLT #8:
+ *
+ * 8. `h = SHA-256(h || c)`
+ * * The received ciphertext is mixed into the handshake digest.
+ * This step serves to ensure the payload wasn't modified by a
+ * MITM.
+ */
+ sha_mix_in(&h->h, h->act2.tag, sizeof(h->act2.tag));
+
+ return act_three_initiator(ln, h);
+}
+
+// Prepare the very first message and send it the connected node
+// Wait for a response in act2
+int act_one_initiator(struct lnsocket *ln, struct handshake *h)
+{
+ h->e = generate_key(ln->secp);
+
+ /* BOLT #8:
+ *
+ * 2. `h = SHA-256(h || e.pub.serializeCompressed())`
+ * * The newly generated ephemeral key is accumulated into the
+ * running handshake digest.
+ */
+ sha_mix_in_key(ln->secp, &h->h, &h->e.pub);
+
+ /* BOLT #8:
+ *
+ * 3. `es = ECDH(e.priv, rs)`
+ * * The initiator performs an ECDH between its newly generated ephemeral
+ * key and the remote node's static public key.
+ */
+ if (!secp256k1_ecdh(ln->secp, h->ss.data,
+ &h->their_id.pubkey, h->e.priv.secret.data,
+ NULL, NULL)) {
+ note_error(&ln->errs, "handshake failed, secp256k1_ecdh error");
+ return 0;
+ }
+
+ /* BOLT #8:
+ *
+ * 4. `ck, temp_k1 = HKDF(ck, es)`
+ * * A new temporary encryption key is generated, which is
+ * used to generate the authenticating MAC.
+ */
+ hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss));
+
+ /* BOLT #8:
+ * 5. `c = encryptWithAD(temp_k1, 0, h, zero)`
+ * * where `zero` is a zero-length plaintext
+ */
+ encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0,
+ h->act1.tag, sizeof(h->act1.tag));
+
+ /* BOLT #8:
+ * 6. `h = SHA-256(h || c)`
+ * * Finally, the generated ciphertext is accumulated into the
+ * authenticating handshake digest.
+ */
+ sha_mix_in(&h->h, h->act1.tag, sizeof(h->act1.tag));
+
+ /* BOLT #8:
+ *
+ * 7. Send `m = 0 || e.pub.serializeCompressed() || c` to the responder over the network buffer.
+ */
+ h->act1.v = 0;
+ size_t len = sizeof(h->act1.pubkey);
+ secp256k1_ec_pubkey_serialize(ln->secp, h->act1.pubkey, &len,
+ &h->e.pub.pubkey,
+ SECP256K1_EC_COMPRESSED);
+
+ check_act_one(&h->act1);
+
+ if (write(ln->socket, &h->act1, ACT_ONE_SIZE) != ACT_ONE_SIZE) {
+ note_error(&ln->errs, "handshake failed on initial send");
+ return 0;
+ }
+
+ return act_two_initiator(ln, h);
+}
diff --git a/handshake.h b/handshake.h
@@ -22,9 +22,11 @@ THE SOFTWARE.
#ifndef LNLINK_HANDSHAKE_H
#define LNLINK_HANDSHAKE_H
-#include "types.h"
+#include "typedefs.h"
#include "sha256.h"
+#include <netdb.h>
+
#include <sodium/crypto_aead_chacha20poly1305.h>
#include <secp256k1_extrakeys.h>
@@ -138,8 +140,6 @@ struct handshake {
/* Where is connection from/to */
struct addrinfo addr;
- /* Who we are */
- struct keypair my_id;
/* Who they are: set already if we're initiator. */
struct pubkey their_id;
@@ -157,4 +157,12 @@ struct handshake {
*/
};
+void new_handshake(secp256k1_context *secp, struct handshake *handshake,
+ const struct pubkey *responder_id);
+
+struct lnsocket;
+
+int act_one_initiator(struct lnsocket *ln, struct handshake *h);
+struct keypair generate_key(secp256k1_context *ctx);
+
#endif /* LNLINK_HANDSHAKE_H */
diff --git a/lnsocket.c b/lnsocket.c
@@ -15,49 +15,43 @@
#include <sodium/crypto_aead_chacha20poly1305.h>
#include <sodium/randombytes.h>
-#include "sha256.h"
-#include "compiler.h"
-#include "hkdf.h"
#include "handshake.h"
-#include "endian.h"
+#include "error.h"
+#include "compiler.h"
+#include "lnsocket_internal.h"
#define array_len(x) (sizeof(x)/sizeof(x[0]))
-struct lnsocket {
- const char *errors[8];
- int socket;
- int num_errors;
- secp256k1_context *secp;
-};
-
+int push_error(struct lnsocket *lnsocket, const char *err);
-static bool char_to_hex(unsigned char *val, char c)
+static int char_to_hex(unsigned char *val, char c)
{
if (c >= '0' && c <= '9') {
*val = c - '0';
- return true;
+ return 1;
}
if (c >= 'a' && c <= 'f') {
*val = c - 'a' + 10;
- return true;
+ return 1;
}
if (c >= 'A' && c <= 'F') {
*val = c - 'A' + 10;
- return true;
+ return 1;
}
- return false;
+ return 0;
}
-static bool hex_decode(const char *str, size_t slen, void *buf, size_t bufsize)
+
+static int hex_decode(const char *str, size_t slen, void *buf, size_t bufsize)
{
unsigned char v1, v2;
unsigned char *p = buf;
while (slen > 1) {
if (!char_to_hex(&v1, str[0]) || !char_to_hex(&v2, str[1]))
- return false;
+ return 0;
if (!bufsize)
- return false;
+ return 0;
*(p++) = (v1 << 4) | v2;
str += 2;
slen -= 2;
@@ -66,12 +60,13 @@ static bool hex_decode(const char *str, size_t slen, void *buf, size_t bufsize)
return slen == 0 && bufsize == 0;
}
-int parse_node_id(const char *str, struct node_id *dest)
+
+static int parse_node_id(const char *str, struct node_id *dest)
{
return hex_decode(str, strlen(str), dest->k, sizeof(*dest));
}
-int pubkey_from_node_id(secp256k1_context *secp, struct pubkey *key,
+static int pubkey_from_node_id(secp256k1_context *secp, struct pubkey *key,
const struct node_id *id)
{
return secp256k1_ec_pubkey_parse(secp, &key->pubkey,
@@ -79,513 +74,119 @@ int pubkey_from_node_id(secp256k1_context *secp, struct pubkey *key,
sizeof(id->k));
}
-
-static struct keypair generate_key(secp256k1_context *ctx)
-{
- struct keypair k;
-
- do {
- randombytes_buf(k.priv.secret.data, sizeof(k.priv.secret.data));
- } while (!secp256k1_ec_pubkey_create(ctx, &k.pub.pubkey,
- k.priv.secret.data));
- return k;
-}
-
-
-static int push_error(struct lnsocket *lnsocket, const char *err)
+struct lnsocket *lnsocket_create_with(int memory)
{
- if (lnsocket->num_errors >= array_len(lnsocket->errors)) {
- // TODO: push out old errors instead
- return 0;
- }
+ struct cursor mem;
+ void *arena = malloc(memory);
- lnsocket->errors[lnsocket->num_errors++] = err;
+ if (!arena)
+ return NULL;
- return 0;
-}
+ make_cursor(arena, arena + memory, &mem);
+ struct lnsocket *lnsocket = cursor_alloc(&mem, sizeof(*lnsocket));
-static void lnsocket_init(struct lnsocket *lnsocket)
-{
- memset(lnsocket, 0, sizeof(*lnsocket));
+ if (!lnsocket)
+ return NULL;
lnsocket->secp = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY |
SECP256K1_CONTEXT_SIGN);
-}
-
-/* h = SHA-256(h || data) */
-static void sha_mix_in(struct sha256 *h, const void *data, size_t len)
-{
- struct sha256_ctx shactx;
-
- sha256_init(&shactx);
- sha256_update(&shactx, h->u.u8, sizeof(*h));
- sha256_update(&shactx, data, len);
- sha256_done(&shactx, h);
-}
-
-/* h = SHA-256(h || pub.serializeCompressed()) */
-static void sha_mix_in_key(secp256k1_context *ctx, struct sha256 *h,
- const struct pubkey *key)
-{
- u8 der[PUBKEY_CMPR_LEN];
- size_t len = sizeof(der);
-
- secp256k1_ec_pubkey_serialize(ctx, der, &len, &key->pubkey,
- SECP256K1_EC_COMPRESSED);
- assert(len == sizeof(der));
- sha_mix_in(h, der, sizeof(der));
-}
-
-/* out1, out2 = HKDF(in1, in2)` */
-static void hkdf_two_keys(struct secret *out1, struct secret *out2,
- const struct secret *in1,
- const void *in2, size_t in2_size)
-{
- /* BOLT #8:
- *
- * * `HKDF(salt,ikm)`: a function defined in `RFC 5869`<sup>[3](#reference-3)</sup>,
- * evaluated with a zero-length `info` field
- * * All invocations of `HKDF` implicitly return 64 bytes
- * of cryptographic randomness using the extract-and-expand
- * component of the `HKDF`.
- */
- struct secret okm[2];
-
- hkdf_sha256(okm, sizeof(okm), in1, sizeof(*in1), in2, in2_size,
- NULL, 0);
- *out1 = okm[0];
- *out2 = okm[1];
-}
-
-
-static void le64_nonce(unsigned char *npub, u64 nonce)
-{
- /* BOLT #8:
- *
- * ...with nonce `n` encoded as 32 zero bits, followed by a
- * *little-endian* 64-bit value. Note: this follows the Noise Protocol
- * convention, rather than our normal endian
- */
- le64 le_nonce = cpu_to_le64(nonce);
- const size_t zerolen = crypto_aead_chacha20poly1305_ietf_NPUBBYTES - sizeof(le_nonce);
-
- BUILD_ASSERT(crypto_aead_chacha20poly1305_ietf_NPUBBYTES >= sizeof(le_nonce));
- /* First part is 0, followed by nonce. */
- memset(npub, 0, zerolen);
- memcpy(npub + zerolen, &le_nonce, sizeof(le_nonce));
-}
-
-/* BOLT #8:
- * * `encryptWithAD(k, n, ad, plaintext)`: outputs `encrypt(k, n, ad,
- * plaintext)`
- * * Where `encrypt` is an evaluation of `ChaCha20-Poly1305` (IETF
- * variant) with the passed arguments, with nonce `n`
- */
-static void encrypt_ad(const struct secret *k, u64 nonce,
- const void *additional_data, size_t additional_data_len,
- const void *plaintext, size_t plaintext_len,
- void *output, size_t outputlen)
-{
- unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
- unsigned long long clen;
- int ret;
-
- assert(outputlen == plaintext_len + crypto_aead_chacha20poly1305_ietf_ABYTES);
- le64_nonce(npub, nonce);
- BUILD_ASSERT(sizeof(*k) == crypto_aead_chacha20poly1305_ietf_KEYBYTES);
-
- ret = crypto_aead_chacha20poly1305_ietf_encrypt(
- output, &clen, memcheck(plaintext, plaintext_len),
- plaintext_len, additional_data, additional_data_len,
- NULL, npub, k->data);
-
- assert(ret == 0);
- assert(clen == plaintext_len + crypto_aead_chacha20poly1305_ietf_ABYTES);
-}
-static inline void check_act_one(const struct act_one *act1)
-{
- /* BOLT #8:
- *
- * : 1 byte for the handshake version, 33 bytes for the compressed
- * ephemeral public key of the initiator, and 16 bytes for the
- * `poly1305` tag.
- */
- BUILD_ASSERT(sizeof(act1->v) == 1);
- BUILD_ASSERT(sizeof(act1->pubkey) == 33);
- BUILD_ASSERT(sizeof(act1->tag) == 16);
-}
-
-static void print_hex(u8 *bytes, int len) {
- int i;
- for (i = 0; i < len; ++i) {
- printf("%02x", bytes[i]);
+ if (!cursor_slice(&mem, &lnsocket->errs.cur, memory / 2)) {
+ return NULL;
}
-}
+ lnsocket->errs.enabled = 1;
-static void new_handshake(secp256k1_context *secp, struct handshake *handshake,
- const struct pubkey *responder_id)
-{
- /* BOLT #8:
- *
- * Before the start of Act One, both sides initialize their
- * per-sessions state as follows:
- *
- * 1. `h = SHA-256(protocolName)`
- * * where `protocolName = "Noise_XK_secp256k1_ChaChaPoly_SHA256"`
- * encoded as an ASCII string
- */
- sha256(&handshake->h, "Noise_XK_secp256k1_ChaChaPoly_SHA256",
- strlen("Noise_XK_secp256k1_ChaChaPoly_SHA256"));
-
- /* BOLT #8:
- *
- * 2. `ck = h`
- */
- BUILD_ASSERT(sizeof(handshake->h) == sizeof(handshake->ck));
- memcpy(&handshake->ck, &handshake->h, sizeof(handshake->ck));
-
- /* BOLT #8:
- *
- * 3. `h = SHA-256(h || prologue)`
- * * where `prologue` is the ASCII string: `lightning`
- */
- sha_mix_in(&handshake->h, "lightning", strlen("lightning"));
-
- /* BOLT #8:
- *
- * As a concluding step, both sides mix the responder's public key
- * into the handshake digest:
- *
- * * The initiating node mixes in the responding node's static public
- * key serialized in Bitcoin's compressed format:
- * * `h = SHA-256(h || rs.pub.serializeCompressed())`
- *
- * * The responding node mixes in their local static public key
- * serialized in Bitcoin's compressed format:
- * * `h = SHA-256(h || ls.pub.serializeCompressed())`
- */
- sha_mix_in_key(secp, &handshake->h, responder_id);
+ lnsocket->mem = mem;
+ return lnsocket;
}
-static void print_act_two(struct act_two *two)
+struct lnsocket *lnsocket_create()
{
- printf("ACT2 v %d pubkey ", two->v);
- print_hex(two->pubkey, sizeof(two->pubkey));
- printf(" tag ");
- print_hex(two->tag, sizeof(two->tag));
- printf("\n");
+ return lnsocket_create_with(16384);
}
-/* BOLT #8:
- * * `decryptWithAD(k, n, ad, ciphertext)`: outputs `decrypt(k, n, ad,
- * ciphertext)`
- * * Where `decrypt` is an evaluation of `ChaCha20-Poly1305` (IETF
- * variant) with the passed arguments, with nonce `n`
- */
-static int decrypt(const struct secret *k, u64 nonce,
- const void *additional_data, size_t additional_data_len,
- const void *ciphertext, size_t ciphertext_len,
- void *output, size_t outputlen)
+void lnsocket_destroy(struct lnsocket *lnsocket)
{
- unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES];
- unsigned long long mlen;
-
- assert(outputlen == ciphertext_len - crypto_aead_chacha20poly1305_ietf_ABYTES);
-
- le64_nonce(npub, nonce);
- BUILD_ASSERT(sizeof(*k) == crypto_aead_chacha20poly1305_ietf_KEYBYTES);
- if (crypto_aead_chacha20poly1305_ietf_decrypt(output, &mlen, NULL,
- memcheck(ciphertext, ciphertext_len),
- ciphertext_len,
- additional_data, additional_data_len,
- npub, k->data) != 0) {
- return 0;
- }
-
- assert(mlen == ciphertext_len - crypto_aead_chacha20poly1305_ietf_ABYTES);
- return 1;
-}
-
-static int act_three_initiator(struct lnsocket *ln, struct handshake *h)
-{
- u8 spub[PUBKEY_CMPR_LEN];
- size_t len = sizeof(spub);
-
- /* BOLT #8:
- * 1. `c = encryptWithAD(temp_k2, 1, h, s.pub.serializeCompressed())`
- * * where `s` is the static public key of the initiator
- */
- secp256k1_ec_pubkey_serialize(ln->secp, spub, &len,
- &h->my_id.pub.pubkey,
- SECP256K1_EC_COMPRESSED);
- encrypt_ad(&h->temp_k, 1, &h->h, sizeof(h->h), spub, sizeof(spub),
- h->act3.ciphertext, sizeof(h->act3.ciphertext));
-
- /* BOLT #8:
- * 2. `h = SHA-256(h || c)`
- */
- sha_mix_in(&h->h, h->act3.ciphertext, sizeof(h->act3.ciphertext));
-
- /* BOLT #8:
- *
- * 3. `se = ECDH(s.priv, re)`
- * * where `re` is the ephemeral public key of the responder
- */
- if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->re.pubkey,
- h->my_id.priv.secret.data, NULL, NULL))
- return push_error(ln, "act3 ecdh handshake failed");
-
- /* BOLT #8:
- *
- * 4. `ck, temp_k3 = HKDF(ck, se)`
- * * The final intermediate shared secret is mixed into the running chaining key.
- */
- hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss));
-
- /* BOLT #8:
- *
- * 5. `t = encryptWithAD(temp_k3, 0, h, zero)`
- * * where `zero` is a zero-length plaintext
- *
- */
- encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0,
- h->act3.tag, sizeof(h->act3.tag));
-
- /* BOLT #8:
- *
- * 8. Send `m = 0 || c || t` over the network buffer.
- *
- */
- h->act3.v = 0;
-
- if (write(ln->socket, &h->act3, ACT_THREE_SIZE) != ACT_THREE_SIZE) {
- return push_error(ln, "handshake failed on initial send");
- }
-
- printf("handshake success!\n");
- return 0;
-}
-
-// act2: read the response to the message sent in act1
-static int act_two_initiator(struct lnsocket *ln, struct handshake *h)
-{
- /* BOLT #8:
- *
- * 1. Read _exactly_ 50 bytes from the network buffer.
- *
- * 2. Parse the read message (`m`) into `v`, `re`, and `c`:
- * * where `v` is the _first_ byte of `m`, `re` is the next 33
- * bytes of `m`, and `c` is the last 16 bytes of `m`.
- */
- ssize_t size;
-
- if ((size = read(ln->socket, &h->act2, ACT_TWO_SIZE)) != ACT_TWO_SIZE) {
- printf("read %ld bytes, expected %d\n", size, ACT_TWO_SIZE);
- return push_error(ln, strerror(errno));
- }
-
- print_act_two(&h->act2);
-
- /* BOLT #8:
- *
- * 3. If `v` is an unrecognized handshake version, then the responder
- * MUST abort the connection attempt.
- */
- if (h->act2.v != 0)
- return push_error(ln, "unrecognized handshake version");
-
- /* BOLT #8:
- *
- * * The raw bytes of the remote party's ephemeral public key
- * (`re`) are to be deserialized into a point on the curve using
- * affine coordinates as encoded by the key's serialized
- * composed format.
- */
- if (secp256k1_ec_pubkey_parse(ln->secp, &h->re.pubkey, h->act2.pubkey,
- sizeof(h->act2.pubkey)) != 1) {
- return push_error(ln, "failed to parse remote pubkey");
- }
+ if (!lnsocket)
+ return;
- /* BOLT #8:
- *
- * 4. `h = SHA-256(h || re.serializeCompressed())`
- */
- sha_mix_in_key(ln->secp, &h->h, &h->re);
-
- /* BOLT #8:
- *
- * 5. `es = ECDH(s.priv, re)`
- */
- if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->re.pubkey,
- h->e.priv.secret.data, NULL, NULL)) {
- return push_error(ln, "act2 ecdh failed");
- }
-
- /* BOLT #8:
- *
- * 6. `ck, temp_k2 = HKDF(ck, ee)`
- * * A new temporary encryption key is generated, which is
- * used to generate the authenticating MAC.
- */
- hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss));
-
- /* BOLT #8:
- *
- * 7. `p = decryptWithAD(temp_k2, 0, h, c)`
- * * If the MAC check in this operation fails, then the initiator
- * MUST terminate the connection without any further messages.
- */
- if (!decrypt(&h->temp_k, 0, &h->h, sizeof(h->h),
- h->act2.tag, sizeof(h->act2.tag), NULL, 0)) {
- return push_error(ln, "handshake decrypt failed");
- }
-
- /* BOLT #8:
- *
- * 8. `h = SHA-256(h || c)`
- * * The received ciphertext is mixed into the handshake digest.
- * This step serves to ensure the payload wasn't modified by a
- * MITM.
- */
- sha_mix_in(&h->h, h->act2.tag, sizeof(h->act2.tag));
-
- return act_three_initiator(ln, h);
+ secp256k1_context_destroy(lnsocket->secp);
+ free(lnsocket->mem.start);
}
-// Prepare the very first message and send it the connected node
-// Wait for a response in act2
-int act_one_initiator(struct lnsocket *ln, struct handshake *h)
+static int is_zero(void *vp, int size)
{
- h->e = generate_key(ln->secp);
-
- /* BOLT #8:
- *
- * 2. `h = SHA-256(h || e.pub.serializeCompressed())`
- * * The newly generated ephemeral key is accumulated into the
- * running handshake digest.
- */
- sha_mix_in_key(ln->secp, &h->h, &h->e.pub);
-
- /* BOLT #8:
- *
- * 3. `es = ECDH(e.priv, rs)`
- * * The initiator performs an ECDH between its newly generated ephemeral
- * key and the remote node's static public key.
- */
- if (!secp256k1_ecdh(ln->secp, h->ss.data,
- &h->their_id.pubkey, h->e.priv.secret.data,
- NULL, NULL)) {
- push_error(ln, "handshake failed, secp256k1_ecdh error");
- return 0;
- }
+ u8 *p = (u8*)vp;
+ const u8 *start = p;
- /* BOLT #8:
- *
- * 4. `ck, temp_k1 = HKDF(ck, es)`
- * * A new temporary encryption key is generated, which is
- * used to generate the authenticating MAC.
- */
- hkdf_two_keys(&h->ck, &h->temp_k, &h->ck, &h->ss, sizeof(h->ss));
-
- /* BOLT #8:
- * 5. `c = encryptWithAD(temp_k1, 0, h, zero)`
- * * where `zero` is a zero-length plaintext
- */
- encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0,
- h->act1.tag, sizeof(h->act1.tag));
-
- /* BOLT #8:
- * 6. `h = SHA-256(h || c)`
- * * Finally, the generated ciphertext is accumulated into the
- * authenticating handshake digest.
- */
- sha_mix_in(&h->h, h->act1.tag, sizeof(h->act1.tag));
-
- /* BOLT #8:
- *
- * 7. Send `m = 0 || e.pub.serializeCompressed() || c` to the responder over the network buffer.
- */
- h->act1.v = 0;
- size_t len = sizeof(h->act1.pubkey);
- secp256k1_ec_pubkey_serialize(ln->secp, h->act1.pubkey, &len,
- &h->e.pub.pubkey,
- SECP256K1_EC_COMPRESSED);
-
- check_act_one(&h->act1);
-
- if (write(ln->socket, &h->act1, ACT_ONE_SIZE) != ACT_ONE_SIZE) {
- push_error(ln, "handshake failed on initial send");
- return 0;
+ for (; p < start+size; p++) {
+ if (*p != 0)
+ return 0;
}
-
- return act_two_initiator(ln, h);
+ return 1;
}
-int connect_node(struct lnsocket *ln, const char *node_id, const char *host)
+int lnsocket_connect(struct lnsocket *ln, const char *node_id, const char *host)
{
int ret;
struct addrinfo *addrs = NULL;
struct handshake h;
- struct keypair my_id;
struct pubkey their_id;
struct node_id their_node_id;
+ if (is_zero(&ln->key, sizeof(ln->key))) {
+ note_error(&ln->errs, "key not initialized, use lnsocket_set_key() or lnsocket_genkey()");
+ return 0;
+ }
+
// convert node_id string to bytes
if (!parse_node_id(node_id, &their_node_id)) {
- push_error(ln, "failed to parse node id");
+ note_error(&ln->errs, "failed to parse node id");
return 0;
}
// encode node_id bytes to secp pubkey
if (!pubkey_from_node_id(ln->secp, &their_id, &their_node_id)) {
- push_error(ln, "failed to convert node_id to pubkey");
+ note_error(&ln->errs, "failed to convert node_id to pubkey");
return 0;
}
// parse ip into addrinfo
if ((ret = getaddrinfo(host, "9735", NULL, &addrs)) || !addrs) {
- push_error(ln, gai_strerror(ret));
+ note_error(&ln->errs, "%s", gai_strerror(ret));
return 0;
}
// create our network socket for comms
if (!(ln->socket = socket(AF_INET, SOCK_STREAM, 0))) {
- push_error(ln, "creating socket failed");
+ note_error(&ln->errs, "creating socket failed");
return 0;
}
// connect to the node!
if (connect(ln->socket, addrs->ai_addr, addrs->ai_addrlen) == -1) {
- push_error(ln, strerror(errno));
+ note_error(&ln->errs, "%s", strerror(errno));
return 0;
}
// prepare some data for ACT1
- my_id = generate_key(ln->secp);
new_handshake(ln->secp, &h, &their_id);
h.side = INITIATOR;
- h.my_id = my_id;
h.their_id = their_id;
// let's do this!
return act_one_initiator(ln, &h);
}
-int main(int argc, const char *argv[])
+void lnsocket_genkey(struct lnsocket *ln)
{
- struct lnsocket ln;
- lnsocket_init(&ln);
-
- const char *nodeid = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71";
+ ln->key = generate_key(ln->secp);
+}
- if (!connect_node(&ln, nodeid, "24.84.152.187")) {
- printf("connection failed: %s\n", ln.errors[0]);
- return 1;
- }
+void lnsocket_print_errors(struct lnsocket *ln)
+{
+ print_error_backtrace(&ln->errs);
+}
- printf("connected!\n");
- return 0;
-}
diff --git a/lnsocket.h b/lnsocket.h
@@ -0,0 +1,12 @@
+#ifndef LNSOCKET_H
+#define LNSOCKET_H
+
+struct lnsocket;
+
+int lnsocket_connect(struct lnsocket *, const char *node_id, const char *host);
+void lnsocket_genkey(struct lnsocket *);
+struct lnsocket *lnsocket_create();
+void lnsocket_destroy(struct lnsocket *);
+void lnsocket_print_errors(struct lnsocket *);
+
+#endif /* LNSOCKET_H */
diff --git a/lnsocket_internal.h b/lnsocket_internal.h
@@ -0,0 +1,11 @@
+
+struct lnsocket {
+ const char *errors[8];
+ struct cursor mem;
+ struct errors errs;
+ int socket;
+ int num_errors;
+ struct keypair key;
+ secp256k1_context *secp;
+};
+
diff --git a/test.c b/test.c
@@ -0,0 +1,22 @@
+
+#include "lnsocket.h"
+#include <stdio.h>
+
+int main(int argc, const char *argv[])
+{
+ struct lnsocket *ln = lnsocket_create();
+ int ok = 1;
+
+ const char *nodeid = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71";
+
+ lnsocket_genkey(ln);
+ if (!(ok = lnsocket_connect(ln, nodeid, "24.84.152.187"))) {
+ lnsocket_print_errors(ln);
+ goto done;
+ }
+
+ printf("connected!\n");
+done:
+ lnsocket_destroy(ln);
+ return !ok;
+}
diff --git a/typedefs.h b/typedefs.h
@@ -0,0 +1,13 @@
+
+#ifndef LNSOCKET_TYPES_H
+#define LNSOCKET_TYPES_H
+#include <stdint.h>
+
+typedef unsigned char u8;
+typedef unsigned int u32;
+typedef unsigned short u16;
+typedef uint64_t u64;
+typedef int64_t s64;
+
+
+#endif /* LNSOCKET_TYPES_H */
diff --git a/types.h b/types.h
@@ -1,8 +0,0 @@
-
-#ifndef LNSOCKET_TYPES_H
-#define LNSOCKET_TYPES_H
-
-typedef unsigned char u8;
-typedef uint64_t u64;
-
-#endif /* LNSOCKET_TYPES_H */
diff --git a/varint.c b/varint.c
@@ -0,0 +1,75 @@
+
+#include "varint.h"
+
+size_t varint_size(uint64_t v)
+{
+ if (v < 0xfd)
+ return 1;
+ if (v <= 0xffff)
+ return 3;
+ if (v <= 0xffffffff)
+ return 5;
+ return 9;
+}
+
+size_t varint_put(unsigned char buf[VARINT_MAX_LEN], uint64_t v)
+{
+ unsigned char *p = buf;
+
+ if (v < 0xfd) {
+ *(p++) = v;
+ } else if (v <= 0xffff) {
+ (*p++) = 0xfd;
+ (*p++) = v;
+ (*p++) = v >> 8;
+ } else if (v <= 0xffffffff) {
+ (*p++) = 0xfe;
+ (*p++) = v;
+ (*p++) = v >> 8;
+ (*p++) = v >> 16;
+ (*p++) = v >> 24;
+ } else {
+ (*p++) = 0xff;
+ (*p++) = v;
+ (*p++) = v >> 8;
+ (*p++) = v >> 16;
+ (*p++) = v >> 24;
+ (*p++) = v >> 32;
+ (*p++) = v >> 40;
+ (*p++) = v >> 48;
+ (*p++) = v >> 56;
+ }
+ return p - buf;
+}
+
+size_t varint_get(const unsigned char *p, size_t max, int64_t *val)
+{
+ if (max < 1)
+ return 0;
+
+ switch (*p) {
+ case 0xfd:
+ if (max < 3)
+ return 0;
+ *val = ((uint64_t)p[2] << 8) + p[1];
+ return 3;
+ case 0xfe:
+ if (max < 5)
+ return 0;
+ *val = ((uint64_t)p[4] << 24) + ((uint64_t)p[3] << 16)
+ + ((uint64_t)p[2] << 8) + p[1];
+ return 5;
+ case 0xff:
+ if (max < 9)
+ return 0;
+ *val = ((uint64_t)p[8] << 56) + ((uint64_t)p[7] << 48)
+ + ((uint64_t)p[6] << 40) + ((uint64_t)p[5] << 32)
+ + ((uint64_t)p[4] << 24) + ((uint64_t)p[3] << 16)
+ + ((uint64_t)p[2] << 8) + p[1];
+ return 9;
+ default:
+ *val = *p;
+ return 1;
+ }
+}
+
diff --git a/varint.h b/varint.h
@@ -0,0 +1,14 @@
+
+#ifndef LNSOCKET_VARINT_H
+#define LNSOCKET_VARINT_H
+
+#define VARINT_MAX_LEN 9
+
+#include <stddef.h>
+#include <stdint.h>
+
+size_t varint_put(unsigned char buf[VARINT_MAX_LEN], uint64_t v);
+size_t varint_size(uint64_t v);
+size_t varint_get(const unsigned char *p, size_t max, int64_t *val);
+
+#endif /* PROTOVERSE_VARINT_H */