lnsocket

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

commit f9791a1d58272c921861c08a361a0006a00b82bc
parent 9f829c2d57ffc39298ec1a88b1289a55f8838323
Author: William Casarin <jb55@jb55.com>
Date:   Sat, 15 Jan 2022 17:24:19 -0800

handshake success!

Diffstat:
Mhandshake.h | 4+++-
Mhkdf.c | 2+-
Mlnsocket.c | 197++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
3 files changed, 184 insertions(+), 19 deletions(-)

diff --git a/handshake.h b/handshake.h @@ -29,8 +29,10 @@ THE SOFTWARE. #include <secp256k1_extrakeys.h> #define PUBKEY_CMPR_LEN 33 + #define ACT_ONE_SIZE 50 #define ACT_TWO_SIZE 50 +#define ACT_THREE_SIZE 66 struct node_id { u8 k[PUBKEY_CMPR_LEN]; @@ -137,7 +139,7 @@ struct handshake { struct addrinfo addr; /* Who we are */ - struct pubkey my_id; + struct keypair my_id; /* Who they are: set already if we're initiator. */ struct pubkey their_id; diff --git a/hkdf.c b/hkdf.c @@ -1,5 +1,5 @@ +/* MIT (BSD) license */ -/* MIT (BSD) license - see LICENSE file for details */ #include "hmac.h" #include "sha256.h" #include <assert.h> diff --git a/lnsocket.c b/lnsocket.c @@ -92,14 +92,16 @@ static struct keypair generate_key(secp256k1_context *ctx) } -static void push_error(struct lnsocket *lnsocket, const char *err) +static int push_error(struct lnsocket *lnsocket, const char *err) { if (lnsocket->num_errors >= array_len(lnsocket->errors)) { // TODO: push out old errors instead - return; + return 0; } lnsocket->errors[lnsocket->num_errors++] = err; + + return 0; } static void lnsocket_init(struct lnsocket *lnsocket) @@ -134,12 +136,6 @@ static void sha_mix_in_key(secp256k1_context *ctx, struct sha256 *h, sha_mix_in(h, der, sizeof(der)); } -static int handshake_failed(struct lnsocket *ln, struct handshake *h) -{ - push_error(ln, "handshake failed"); - return 0; -} - /* out1, out2 = HKDF(in1, in2)` */ static void hkdf_two_keys(struct secret *out1, struct secret *out2, const struct secret *in1, @@ -276,11 +272,102 @@ 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 "); + 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, + &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: @@ -295,14 +382,79 @@ static int act_two_initiator(struct lnsocket *ln, struct handshake *h) 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; + return push_error(ln, strerror(errno)); } print_act_two(&h->act2); - return 0; + + /* 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"); + } + + /* 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); } +// 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); @@ -323,8 +475,10 @@ int act_one_initiator(struct lnsocket *ln, struct handshake *h) */ if (!secp256k1_ecdh(ln->secp, h->ss.data, &h->their_id.pubkey, h->e.priv.secret.data, - NULL, NULL)) - return handshake_failed(ln, h); + NULL, NULL)) { + push_error(ln, "handshake failed, secp256k1_ecdh error"); + return 0; + } /* BOLT #8: * @@ -360,8 +514,10 @@ int act_one_initiator(struct lnsocket *ln, struct handshake *h) check_act_one(&h->act1); - if (write(ln->socket, &h->act1, ACT_ONE_SIZE) != ACT_ONE_SIZE) - return handshake_failed(ln, h); + if (write(ln->socket, &h->act1, ACT_ONE_SIZE) != ACT_ONE_SIZE) { + push_error(ln, "handshake failed on initial send"); + return 0; + } return act_two_initiator(ln, h); } @@ -375,38 +531,45 @@ int connect_node(struct lnsocket *ln, const char *node_id, const char *host) struct pubkey their_id; struct node_id their_node_id; + // convert node_id string to bytes if (!parse_node_id(node_id, &their_node_id)) { push_error(ln, "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"); return 0; } + // parse ip into addrinfo if ((ret = getaddrinfo(host, "9735", NULL, &addrs)) || !addrs) { push_error(ln, 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"); return 0; } + // connect to the node! if (connect(ln->socket, addrs->ai_addr, addrs->ai_addrlen) == -1) { push_error(ln, 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.pub; + h.my_id = my_id; h.their_id = their_id; + // let's do this! return act_one_initiator(ln, &h); }