lnsocket

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

commit 87ec074b41f316efabac3279dc8d2786cdcbc3ef
parent 3edf959aa0976e7dd209d71e9895fd524023f7f6
Author: William Casarin <jb55@jb55.com>
Date:   Sat, 15 Jan 2022 07:44:43 -0800

got to ACT2!

Diffstat:
MMakefile | 4++--
Mhandshake.h | 6++++++
Mlnsocket.c | 213++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 204 insertions(+), 19 deletions(-)

diff --git a/Makefile b/Makefile @@ -3,7 +3,7 @@ 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 +OBJS=sha256.o hkdf.o hmac.o sha512.o lnsocket.o DEPS=$(OBJS) config.h all: lnsocket @@ -37,7 +37,7 @@ deps/libsodium/src/libsodium/.libs/libsodium.a: deps/libsodium/configure cd deps/libsodium/src/libsodium; \ make -j2 libsodium.la -lnsocket: lnsocket.c $(DEPS) $(ARS) +lnsocket: $(DEPS) $(ARS) $(CC) $(CFLAGS) $^ $(LDFLAGS) -o $@ tags: fake diff --git a/handshake.h b/handshake.h @@ -9,6 +9,12 @@ #include <secp256k1_extrakeys.h> #define PUBKEY_CMPR_LEN 33 +#define ACT_ONE_SIZE 50 +#define ACT_TWO_SIZE 50 + +struct node_id { + u8 k[PUBKEY_CMPR_LEN]; +}; enum bolt8_side { INITIATOR, diff --git a/lnsocket.c b/lnsocket.c @@ -8,6 +8,7 @@ #include <string.h> #include <assert.h> #include <errno.h> +#include <unistd.h> #include <secp256k1.h> #include <secp256k1_ecdh.h> @@ -26,10 +27,59 @@ struct lnsocket { const char *errors[8]; int socket; int num_errors; - secp256k1_context *secp_ctx; + secp256k1_context *secp; }; +static bool char_to_hex(unsigned char *val, char c) +{ + if (c >= '0' && c <= '9') { + *val = c - '0'; + return true; + } + if (c >= 'a' && c <= 'f') { + *val = c - 'a' + 10; + return true; + } + if (c >= 'A' && c <= 'F') { + *val = c - 'A' + 10; + return true; + } + return false; +} + +static bool 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; + if (!bufsize) + return false; + *(p++) = (v1 << 4) | v2; + str += 2; + slen -= 2; + bufsize--; + } + return slen == 0 && bufsize == 0; +} + +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, + const struct node_id *id) +{ + return secp256k1_ec_pubkey_parse(secp, &key->pubkey, + memcheck(id->k, sizeof(id->k)), + sizeof(id->k)); +} + + static struct keypair generate_key(secp256k1_context *ctx) { struct keypair k; @@ -56,7 +106,7 @@ static void lnsocket_init(struct lnsocket *lnsocket) { memset(lnsocket, 0, sizeof(*lnsocket)); - lnsocket->secp_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | + lnsocket->secp = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); } @@ -157,10 +207,105 @@ static void encrypt_ad(const struct secret *k, u64 nonce, 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]); + } +} + +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); +} + +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"); +} + +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); + push_error(ln, strerror(errno)); + return 0; + } + + print_act_two(&h->act2); + return 0; +} int act_one_initiator(struct lnsocket *ln, struct handshake *h) { - h->e = generate_key(ln->secp_ctx); + h->e = generate_key(ln->secp); /* BOLT #8: * @@ -168,7 +313,7 @@ int act_one_initiator(struct lnsocket *ln, struct handshake *h) * * The newly generated ephemeral key is accumulated into the * running handshake digest. */ - sha_mix_in_key(ln->secp_ctx, &h->h, &h->e.pub); + sha_mix_in_key(ln->secp, &h->h, &h->e.pub); /* BOLT #8: * @@ -176,7 +321,7 @@ int act_one_initiator(struct lnsocket *ln, struct handshake *h) * * The initiator performs an ECDH between its newly generated ephemeral * key and the remote node's static public key. */ - if (!secp256k1_ecdh(ln->secp_ctx, h->ss.data, + if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->their_id.pubkey, h->e.priv.secret.data, NULL, NULL)) return handshake_failed(ln, h); @@ -196,16 +341,49 @@ int act_one_initiator(struct lnsocket *ln, struct handshake *h) encrypt_ad(&h->temp_k, 0, &h->h, sizeof(h->h), NULL, 0, h->act1.tag, sizeof(h->act1.tag)); - return 1; + /* 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) + return handshake_failed(ln, h); + + return act_two_initiator(ln, h); } -int connect_node(struct lnsocket *ln, const struct pubkey pubkey, const char *host) +int connect_node(struct lnsocket *ln, const char *node_id, const char *host) { int ret; struct addrinfo *addrs = NULL; struct handshake h; - - h.their_id = pubkey; + struct keypair my_id; + struct pubkey their_id; + struct node_id their_node_id; + + if (!parse_node_id(node_id, &their_node_id)) { + push_error(ln, "failed to parse node id"); + return 0; + } + + if (!pubkey_from_node_id(ln->secp, &their_id, &their_node_id)) { + push_error(ln, "failed to convert node_id to pubkey"); + return 0; + } if ((ret = getaddrinfo(host, "9735", NULL, &addrs)) || !addrs) { push_error(ln, gai_strerror(ret)); @@ -222,22 +400,23 @@ int connect_node(struct lnsocket *ln, const struct pubkey pubkey, const char *ho return 0; } + my_id = generate_key(ln->secp); + new_handshake(ln->secp, &h, &their_id); + + h.side = INITIATOR; + h.my_id = my_id.pub; + h.their_id = their_id; + return act_one_initiator(ln, &h); } -static struct pubkey nodeid = { - .pubkey = { - .data = { - 0x03, 0xf3, 0xc1, 0x08, 0xcc, 0xd5, 0x36, 0xb8, 0x52, 0x68, 0x41, 0xf0, - 0xa5, 0xc5, 0x82, 0x12, 0xbb, 0x9e, 0x65, 0x84, 0xa1, 0xeb, 0x49, 0x30, - 0x80, 0xe7, 0xc1, 0xcc, 0x34, 0xf8, 0x2d, 0xad, 0x71 } } -}; - int main(int argc, const char *argv[]) { struct lnsocket ln; lnsocket_init(&ln); + const char *nodeid = "03f3c108ccd536b8526841f0a5c58212bb9e6584a1eb493080e7c1cc34f82dad71"; + if (!connect_node(&ln, nodeid, "24.84.152.187")) { printf("connection failed: %s\n", ln.errors[0]); return 1;