lnsocket

A minimal C library for connecting to the lightning network
git clone git://jb55.com/lnsocket
Log | Files | Refs | Submodules | README | LICENSE

commit 49d9ca46a254a3ede2cd917aae2c499378873a11
parent c161f4d25ea5e484bc06b7f2381da7203e7945c3
Author: William Casarin <jb55@jb55.com>
Date:   Mon, 17 Jan 2022 20:44:28 -0800

ping/pong working!

Diffstat:
MMakefile | 2+-
MREADME | 2+-
Abigsize.c | 118+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Abigsize.h | 31+++++++++++++++++++++++++++++++
Mcrypto.c | 6+++---
Mcrypto.h | 7+++++++
Mcursor.h | 7+------
Mendian.h | 15+++++++++++++++
Mlnsocket.c | 226++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Mlnsocket.h | 65++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
Mlnsocket_internal.h | 1+
Mtest.c | 80++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----------
12 files changed, 524 insertions(+), 36 deletions(-)

diff --git a/Makefile b/Makefile @@ -5,7 +5,7 @@ LDFLAGS= SUBMODULES=deps/libsodium deps/secp256k1 ARS=deps/secp256k1/.libs/libsecp256k1.a deps/libsodium/src/libsodium/.libs/libsodium.a -OBJS=sha256.o hkdf.o hmac.o sha512.o lnsocket.o error.o handshake.o crypto.o +OBJS=sha256.o hkdf.o hmac.o sha512.o lnsocket.o error.o handshake.o crypto.o bigsize.o DEPS=$(OBJS) config.h all: test diff --git a/README b/README @@ -16,7 +16,7 @@ make Contributing ------------ -Send patches to me at jb55@jb55.com: +Send patches to jb55@jb55.com: $ git config format.subjectPrefix 'PATCH lnsocket' $ git config sendemail.to 'William Casarin <jb55@jb55.com>' diff --git a/bigsize.c b/bigsize.c @@ -0,0 +1,118 @@ + +#include <assert.h> +#include "config.h" +#include "bigsize.h" + +#ifndef SUPERVERBOSE +#define SUPERVERBOSE(...) +#endif + +size_t bigsize_len(bigsize_t v) +{ + if (v < 0xfd) { + return 1; + } else if (v <= 0xffff) { + return 3; + } else if (v <= 0xffffffff) { + return 5; + } else { + return 9; + } +} + +size_t bigsize_put(u8 buf[BIGSIZE_MAX_LEN], bigsize_t v) +{ + u8 *p = buf; + + if (v < 0xfd) { + *(p++) = v; + } else if (v <= 0xffff) { + (*p++) = 0xfd; + (*p++) = v >> 8; + (*p++) = v; + } else if (v <= 0xffffffff) { + (*p++) = 0xfe; + (*p++) = v >> 24; + (*p++) = v >> 16; + (*p++) = v >> 8; + (*p++) = v; + } else { + (*p++) = 0xff; + (*p++) = v >> 56; + (*p++) = v >> 48; + (*p++) = v >> 40; + (*p++) = v >> 32; + (*p++) = v >> 24; + (*p++) = v >> 16; + (*p++) = v >> 8; + (*p++) = v; + } + return p - buf; +} + +size_t bigsize_get(const u8 *p, size_t max, bigsize_t *val) +{ + if (max < 1) { + SUPERVERBOSE("EOF"); + return 0; + } + + switch (*p) { + case 0xfd: + if (max < 3) { + SUPERVERBOSE("unexpected EOF"); + return 0; + } + *val = ((u64)p[1] << 8) + p[2]; + if (*val < 0xfd) { + SUPERVERBOSE("decoded bigsize is not canonical"); + return 0; + } + return 3; + case 0xfe: + if (max < 5) { + SUPERVERBOSE("unexpected EOF"); + return 0; + } + *val = ((u64)p[1] << 24) + ((u64)p[2] << 16) + + ((u64)p[3] << 8) + p[4]; + if ((*val >> 16) == 0) { + SUPERVERBOSE("decoded bigsize is not canonical"); + return 0; + } + return 5; + case 0xff: + if (max < 9) { + SUPERVERBOSE("unexpected EOF"); + return 0; + } + *val = ((u64)p[1] << 56) + ((u64)p[2] << 48) + + ((u64)p[3] << 40) + ((u64)p[4] << 32) + + ((u64)p[5] << 24) + ((u64)p[6] << 16) + + ((u64)p[7] << 8) + p[8]; + if ((*val >> 32) == 0) { + SUPERVERBOSE("decoded bigsize is not canonical"); + return 0; + } + return 9; + default: + *val = *p; + return 1; + } +} + +int cursor_pull_bigsize(struct cursor *cur, bigsize_t *out) +{ + return bigsize_get(cur->p, cur->end - cur->p, out); +} + +int cursor_push_bigsize(struct cursor *cur, const bigsize_t val) +{ + u8 buf[BIGSIZE_MAX_LEN]; + size_t len; + + len = bigsize_put(buf, val); + + return cursor_push(cur, buf, len); +} + diff --git a/bigsize.h b/bigsize.h @@ -0,0 +1,31 @@ +#ifndef LNSOCKET_BIGSIZE_H +#define LNSOCKET_BIGSIZE_H +#include "config.h" +#include "cursor.h" +#include <stddef.h> + +/* typedef for clarity. */ +typedef u64 bigsize_t; + +#define BIGSIZE_MAX_LEN 9 + +/* Returns length of buf used. */ +size_t bigsize_put(unsigned char buf[BIGSIZE_MAX_LEN], bigsize_t v); + +/* Returns 0 on failure, otherwise length (<= max) used. */ +size_t bigsize_get(const unsigned char *p, size_t max, bigsize_t *val); + +/* How many bytes does it take to encode v? */ +size_t bigsize_len(bigsize_t v); + +/* Used for wire generation */ +typedef bigsize_t bigsize; + +/* marshal/unmarshal functions */ +void towire_bigsize(unsigned char **pptr, const bigsize_t val); +bigsize_t fromwire_bigsize(const unsigned char **cursor, size_t *max); + +int cursor_pull_bigsize(struct cursor *cur, bigsize_t *out); +int cursor_push_bigsize(struct cursor *cur, const bigsize_t val); + +#endif /* LNSOCKET_BIGSIZE_H */ diff --git a/crypto.c b/crypto.c @@ -86,7 +86,7 @@ int cryptomsg_decrypt_body(struct crypto_state *cs, const u8 *in, size_t inlen, unsigned char npub[crypto_aead_chacha20poly1305_ietf_NPUBBYTES]; unsigned long long mlen; - if (inlen < 16 || outcap < inlen - 16) + if (inlen < 16 || outcap < cryptomsg_decrypt_size(inlen)) return 0; le64_nonce(npub, cs->rn++); @@ -107,7 +107,7 @@ int cryptomsg_decrypt_body(struct crypto_state *cs, const u8 *in, size_t inlen, return 0; } - assert(mlen == inlen - 16); + assert(mlen == cryptomsg_decrypt_size(inlen)); maybe_rotate_key(&cs->rn, &cs->rk, &cs->r_ck); @@ -152,7 +152,7 @@ u8 *cryptomsg_encrypt_msg(struct crypto_state *cs, const u8 *msg, unsigned long *outlen = sizeof(l) + 16 + mlen + 16; - if (outcap < *outlen) { + if (outcap < mlen) { *outlen = 0; return NULL; } diff --git a/crypto.h b/crypto.h @@ -43,6 +43,13 @@ void hkdf_two_keys(struct secret *out1, struct secret *out2, const struct secret *in1, const struct secret *in2); +int cryptomsg_decrypt_body(struct crypto_state *cs, const u8 *in, size_t inlen, u8 *out, size_t outcap); +int cryptomsg_decrypt_header(struct crypto_state *cs, u8 hdr[18], u16 *lenp); unsigned char *cryptomsg_encrypt_msg(struct crypto_state *cs, const u8 *msg, unsigned long long mlen, u8 *out, size_t *outlen, size_t outcap); +static inline int cryptomsg_decrypt_size(size_t inlen) +{ + return inlen - 16; +} + #endif /* LNSOCKET_CRYPTO_H */ diff --git a/cursor.h b/cursor.h @@ -224,7 +224,7 @@ static inline int cursor_pop(struct cursor *cur, u8 *data, int len) return 1; } -static inline int cursor_push(struct cursor *cursor, u8 *data, int len) +static inline int cursor_push(struct cursor *cursor, const void *data, int len) { if (unlikely(cursor->p + len >= cursor->end)) { return 0; @@ -303,11 +303,6 @@ 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; diff --git a/endian.h b/endian.h @@ -3,6 +3,7 @@ #define CCAN_ENDIAN_H #include <stdint.h> #include "config.h" +#include "cursor.h" /** * BSWAP_16 - reverse bytes in a constant uint16_t value. @@ -357,4 +358,18 @@ typedef beint16_t be16; typedef leint64_t le64; typedef leint32_t le32; typedef leint16_t le16; + +static inline int cursor_push_u16(struct cursor *cursor, u16 i) +{ + be16 v = cpu_to_be16(i); + return cursor_push(cursor, &v, sizeof(v)); +} + +static inline int cursor_push_u64(struct cursor *cur, u64 v) +{ + be64 l = cpu_to_be64(v); + return cursor_push(cur, (u8*)&l, sizeof(l)); +} + + #endif /* CCAN_ENDIAN_H */ diff --git a/lnsocket.c b/lnsocket.c @@ -2,6 +2,7 @@ #include <sys/socket.h> #include <sys/types.h> +#include <inttypes.h> #include <netdb.h> #include <stdio.h> #include <stdlib.h> @@ -17,11 +18,18 @@ #include "handshake.h" #include "error.h" +#include "crypto.h" +#include "endian.h" +#include "bigsize.h" #include "compiler.h" #include "lnsocket_internal.h" +#include "lnsocket.h" #define array_len(x) (sizeof(x)/sizeof(x[0])) +#define MSGBUF_MEM 65536 +#define ERROR_MEM 4096 + int push_error(struct lnsocket *lnsocket, const char *err); static int char_to_hex(unsigned char *val, char c) @@ -74,13 +82,205 @@ static int pubkey_from_node_id(secp256k1_context *secp, struct pubkey *key, sizeof(id->k)); } + +static int read_all(int fd, void *data, size_t size) +{ + while (size) { + ssize_t done; + + done = read(fd, data, size); + if (done < 0 && errno == EINTR) + continue; + if (done <= 0) + return 0; + data = (char *)data + done; + size -= done; + } + + return 1; +} + + +int lnsocket_read(struct lnsocket *ln, unsigned char **buf, int *len) +{ + struct cursor enc, dec; + u8 hdr[18]; + u16 size; + + reset_cursor(&ln->errs.cur); + reset_cursor(&ln->msgbuf); + + if (!read_all(ln->socket, hdr, sizeof(hdr))) + return note_error(&ln->errs,"Failed reading header: %s", + strerror(errno)); + + if (!cryptomsg_decrypt_header(&ln->crypto_state, hdr, &size)) + return note_error(&ln->errs, + "Failed hdr decrypt with rn=%"PRIu64, + ln->crypto_state.rn-1); + + if (!cursor_slice(&ln->msgbuf, &enc, size + 16)) + return note_error(&ln->errs, "out of memory"); + + if (!cursor_slice(&ln->msgbuf, &dec, size)) + return note_error(&ln->errs, "out of memory"); + + if (!read_all(ln->socket, enc.p, enc.end - enc.start)) + return note_error(&ln->errs, "Failed reading body: %s", + strerror(errno)); + + if (!cryptomsg_decrypt_body(&ln->crypto_state, + enc.start, enc.end - enc.start, + dec.start, dec.end - dec.start)) + return note_error(&ln->errs, "error decrypting body"); + + *buf = dec.start; + *len = dec.end - dec.start; + + return 1; +} + +static int highest_byte(unsigned char *buf, int buflen) +{ + int i, highest; + for (i = 0, highest = 0; i < buflen; i++) { + if (buf[i] != 0) + highest = i; + } + return highest; +} + +#define max(a,b) ((a) > (b) ? (a) : (b)) +int lnsocket_set_feature_bit(unsigned char *buf, int buflen, int *newlen, unsigned int bit) +{ + if (newlen == NULL) + return 0; + + if (bit / 8 >= buflen) + return 0; + + *newlen = max(highest_byte(buf, buflen), (bit / 8) + 1); + buf[*newlen - 1 - bit / 8] |= (1 << (bit % 8)); + + return 1; +} +#undef max + +int cursor_push_tlv(struct cursor *cur, const struct tlv *tlv) +{ + /* BOLT #1: + * + * The sending node: + ... + * - MUST minimally encode `type` and `length`. + */ + return cursor_push_bigsize(cur, tlv->type) && + cursor_push_bigsize(cur, tlv->length) && + cursor_push(cur, tlv->value, tlv->length); +} + +int cursor_push_tlvs(struct cursor *cur, const struct tlv **tlvs, int n_tlvs) +{ + int i; + for (i = 0; i < n_tlvs; i++) { + if (!cursor_push_tlv(cur, tlvs[i])) + return 0; + } + + return 1; +} + +int lnsocket_make_network_tlv(unsigned char *buf, int buflen, + const unsigned char **blockids, int num_blockids, + struct tlv *tlv_out) +{ + struct cursor cur; + + if (!tlv_out) + return 0; + + tlv_out->type = 1; + tlv_out->value = buf; + + make_cursor(buf, buf + buflen, &cur); + + for (size_t i = 0; i < num_blockids; i++) { + if (!cursor_push(&cur, memcheck(blockids[i], 32), 32)) + return 0; + } + + tlv_out->length = cur.p - cur.start; + return 1; +} + +int lnsocket_make_ping_msg(unsigned char *buf, int buflen, u16 num_pong_bytes, u16 ignored_bytes, int *outlen) +{ + struct cursor msg; + int i; + + make_cursor(buf, buf + buflen, &msg); + + if (!cursor_push_u16(&msg, WIRE_PING)) + return 0; + if (!cursor_push_u16(&msg, num_pong_bytes)) + return 0; + if (!cursor_push_u16(&msg, ignored_bytes)) + return 0; + for (i = 0; i < ignored_bytes; i++) { + if (!cursor_push_byte(&msg, 0)) + return 0; + } + + *outlen = msg.p - msg.start; + + return 1; +} + +int lnsocket_make_init_msg(unsigned char *buf, int buflen, + const unsigned char *globalfeatures, u16 gflen, + const unsigned char *features, u16 flen, + const struct tlv **tlvs, + unsigned short num_tlvs, + int *outlen) +{ + struct cursor msg; + + make_cursor(buf, buf + buflen, &msg); + + if (!cursor_push_u16(&msg, WIRE_INIT)) + return 0; + + if (!cursor_push_u16(&msg, gflen)) + return 0; + + if (!cursor_push(&msg, globalfeatures, gflen)) + return 0; + + if (!cursor_push_u16(&msg, flen)) + return 0; + + if (!cursor_push(&msg, features, flen)) + return 0; + + if (!cursor_push_tlvs(&msg, tlvs, num_tlvs)) + return 0; + + *outlen = msg.p - msg.start; + + return 1; +} + int lnsocket_write(struct lnsocket *ln, const u8 *msg, int msglen) { - // this is just temporary so we don't need to move the memory cursor - u8 *out = ln->mem.p; - ssize_t outcap = ln->mem.end - ln->mem.p; - ssize_t writelen; + ssize_t writelen, outcap; size_t outlen; + u8 *out; + + // this is just temporary so we don't need to move the memory cursor + reset_cursor(&ln->msgbuf); + + out = ln->msgbuf.start; + outcap = ln->msgbuf.end - ln->msgbuf.start; if (!ln->socket) return note_error(&ln->errs, "not connected"); @@ -100,9 +300,11 @@ int lnsocket_write(struct lnsocket *ln, const u8 *msg, int msglen) return 1; } -struct lnsocket *lnsocket_create_with(int memory) +struct lnsocket *lnsocket_create() { struct cursor mem; + int memory = MSGBUF_MEM + ERROR_MEM + sizeof(struct lnsocket); + void *arena = malloc(memory); if (!arena) @@ -115,22 +317,20 @@ struct lnsocket *lnsocket_create_with(int memory) return NULL; lnsocket->secp = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | - SECP256K1_CONTEXT_SIGN); + SECP256K1_CONTEXT_SIGN); - if (!cursor_slice(&mem, &lnsocket->errs.cur, memory / 2)) { + if (!cursor_slice(&mem, &lnsocket->msgbuf, MSGBUF_MEM)) return NULL; - } + + if (!cursor_slice(&mem, &lnsocket->errs.cur, ERROR_MEM)) + return NULL; + lnsocket->errs.enabled = 1; lnsocket->mem = mem; return lnsocket; } -struct lnsocket *lnsocket_create() -{ - return lnsocket_create_with(16384); -} - void lnsocket_destroy(struct lnsocket *lnsocket) { if (!lnsocket) diff --git a/lnsocket.h b/lnsocket.h @@ -1,12 +1,75 @@ #ifndef LNSOCKET_H #define LNSOCKET_H +#include <stdint.h> +#include <stdlib.h> + struct lnsocket; +enum peer_wire { + WIRE_INIT = 16, + WIRE_ERROR = 17, + WIRE_WARNING = 1, + WIRE_PING = 18, + WIRE_PONG = 19, + WIRE_TX_ADD_INPUT = 66, + WIRE_TX_ADD_OUTPUT = 67, + WIRE_TX_REMOVE_INPUT = 68, + WIRE_TX_REMOVE_OUTPUT = 69, + WIRE_TX_COMPLETE = 70, + WIRE_TX_SIGNATURES = 71, + WIRE_OPEN_CHANNEL = 32, + WIRE_ACCEPT_CHANNEL = 33, + WIRE_FUNDING_CREATED = 34, + WIRE_FUNDING_SIGNED = 35, + WIRE_FUNDING_LOCKED = 36, + WIRE_OPEN_CHANNEL2 = 64, + WIRE_ACCEPT_CHANNEL2 = 65, + WIRE_INIT_RBF = 72, + WIRE_ACK_RBF = 73, + WIRE_SHUTDOWN = 38, + WIRE_CLOSING_SIGNED = 39, + WIRE_UPDATE_ADD_HTLC = 128, + WIRE_UPDATE_FULFILL_HTLC = 130, + WIRE_UPDATE_FAIL_HTLC = 131, + WIRE_UPDATE_FAIL_MALFORMED_HTLC = 135, + WIRE_COMMITMENT_SIGNED = 132, + WIRE_REVOKE_AND_ACK = 133, + WIRE_UPDATE_FEE = 134, + WIRE_UPDATE_BLOCKHEIGHT = 137, + WIRE_CHANNEL_REESTABLISH = 136, + WIRE_ANNOUNCEMENT_SIGNATURES = 259, + WIRE_CHANNEL_ANNOUNCEMENT = 256, + WIRE_NODE_ANNOUNCEMENT = 257, + WIRE_CHANNEL_UPDATE = 258, + WIRE_QUERY_SHORT_CHANNEL_IDS = 261, + WIRE_REPLY_SHORT_CHANNEL_IDS_END = 262, + WIRE_QUERY_CHANNEL_RANGE = 263, + WIRE_REPLY_CHANNEL_RANGE = 264, + WIRE_GOSSIP_TIMESTAMP_FILTER = 265, + WIRE_OBS2_ONION_MESSAGE = 387, + WIRE_ONION_MESSAGE = 513, +}; + +/* A single TLV field, consisting of the data and its associated metadata. */ +struct tlv { + uint64_t type; + size_t length; + unsigned char *value; +}; + +struct lnsocket *lnsocket_create(); + +/* messages */ + +int lnsocket_make_network_tlv(unsigned char *buf, int buflen, const unsigned char **blockids, int num_blockids, struct tlv *tlv_out); +int lnsocket_make_ping_msg(unsigned char *buf, int buflen, unsigned short num_pong_bytes, unsigned short ignored_bytes, int *outlen); +int lnsocket_make_init_msg(unsigned char *buf, int buflen, const unsigned char *globalfeatures, unsigned short gflen, const unsigned char *features, unsigned short flen, const struct tlv **tlvs, unsigned short num_tlvs, int *outlen); + int lnsocket_connect(struct lnsocket *, const char *node_id, const char *host); int lnsocket_write(struct lnsocket *, const unsigned char *msg, int msg_len); +int lnsocket_read(struct lnsocket *, unsigned char **buf, int *len); void lnsocket_genkey(struct lnsocket *); -struct lnsocket *lnsocket_create(); void lnsocket_destroy(struct lnsocket *); void lnsocket_print_errors(struct lnsocket *); diff --git a/lnsocket_internal.h b/lnsocket_internal.h @@ -6,6 +6,7 @@ struct lnsocket { const char *errors[8]; struct cursor mem; + struct cursor msgbuf; struct errors errs; int num_errors; int socket; diff --git a/test.c b/test.c @@ -1,31 +1,89 @@ #include "lnsocket.h" +#include "endian.h" +#include "typedefs.h" #include <stdio.h> +#include <assert.h> + +static void print_data(unsigned char *buf, int len) +{ + int i; + + for (i = 0; i < len; i++) { + printf("%02x", buf[i]); + } + printf("\n"); +} int main(int argc, const char *argv[]) { - struct lnsocket *ln = lnsocket_create(); + static u8 tlvbuf[1024]; + static u8 msgbuf[4096]; + u8 *buf; + u8 global_features[2] = {0}; + u8 features[5] = {0}; + struct tlv network_tlv; + struct lnsocket *ln; + int len; + int ok = 1; - const char *nodeid = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71"; + ln = lnsocket_create(); + assert(ln); lnsocket_genkey(ln); - if (!(ok = lnsocket_connect(ln, nodeid, "24.84.152.187"))) { - lnsocket_print_errors(ln); + + const char *nodeid = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71"; + if (!(ok = lnsocket_connect(ln, nodeid, "24.84.152.187"))) goto done; - } - printf("connected!\n"); + if (!(ok = lnsocket_read(ln, &buf, &len))) + goto done; - const unsigned char msg[] = {'h', 'i'}; + printf("got "); print_data(buf, len); - if (!lnsocket_write(ln, msg, sizeof(msg))) { - lnsocket_print_errors(ln); + const u8 genesis_block[] = { + 0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, 0xc1, 0xa6, 0xa2, 0x46, + 0xae, 0x63, 0xf7, 0x4f, 0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c, + 0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + + const u8 *blockids[] = { genesis_block }; + + if (!(ok = lnsocket_make_network_tlv(tlvbuf, sizeof(tlvbuf), blockids, 1, &network_tlv))) + goto done; + + const struct tlv *init_tlvs[] = { &network_tlv } ; + + if (!(ok = lnsocket_make_init_msg(msgbuf, sizeof(msgbuf), + global_features, sizeof(global_features), + features, sizeof(features), + init_tlvs, 1, + &len))) + goto done; + + if (!(ok = lnsocket_write(ln, msgbuf, len))) + goto done; + + printf("sent init "); + print_data(msgbuf, len); + + if (!(ok = lnsocket_make_ping_msg(msgbuf, sizeof(msgbuf), 1, 1, &len))) + goto done; + + if (!(ok = lnsocket_write(ln, msgbuf, len))) + goto done; + + printf("sent ping "); + print_data(msgbuf, len); + + if (!(ok = lnsocket_read(ln, &buf, &len))) goto done; - } - printf("wrote message.\n"); + printf("got "); + print_data(buf, len); done: + lnsocket_print_errors(ln); lnsocket_destroy(ln); return !ok; }