diff --git a/bitcoin/signature.c b/bitcoin/signature.c index 001ec2b54f4e..ca3bf40a5279 100644 --- a/bitcoin/signature.c +++ b/bitcoin/signature.c @@ -251,6 +251,12 @@ size_t signature_to_der(secp256k1_context *secpctx, return len; } +bool signature_from_der(secp256k1_context *secpctx, + const u8 *der, size_t len, struct signature *sig) +{ + return secp256k1_ecdsa_signature_parse_der(secpctx, &sig->sig, der, len); +} + /* Signature must have low S value. */ bool sig_valid(secp256k1_context *secpctx, const struct signature *sig) { diff --git a/bitcoin/signature.h b/bitcoin/signature.h index d2251ca94c10..534040c24751 100644 --- a/bitcoin/signature.h +++ b/bitcoin/signature.h @@ -58,4 +58,8 @@ bool sig_valid(secp256k1_context *secpctx, const struct signature *sig); size_t signature_to_der(secp256k1_context *secpctx, u8 der[72], const struct signature *s); +/* Parse DER encoding into signature sig */ +bool signature_from_der(secp256k1_context *secpctx, + const u8 *der, size_t len, struct signature *sig); + #endif /* LIGHTNING_BITCOIN_SIGNATURE_H */ diff --git a/daemon/Makefile b/daemon/Makefile index a9d13b5b8cee..60f5cca3fb30 100644 --- a/daemon/Makefile +++ b/daemon/Makefile @@ -26,6 +26,7 @@ DAEMON_SRC := \ daemon/feechange.c \ daemon/htlc.c \ daemon/invoice.c \ + daemon/irc_announce.c \ daemon/jsonrpc.c \ daemon/lightningd.c \ daemon/netaddr.c \ @@ -41,6 +42,7 @@ DAEMON_SRC := \ daemon/wallet.c \ daemon/watch.c \ names.c \ + irc.c \ state.c DAEMON_OBJS := $(DAEMON_SRC:.c=.o) diff --git a/daemon/chaintopology.c b/daemon/chaintopology.c index 87a6880aa02a..684d362de2c3 100644 --- a/daemon/chaintopology.c +++ b/daemon/chaintopology.c @@ -493,6 +493,27 @@ u64 get_feerate(struct lightningd_state *dstate) return dstate->topology->feerate; } +struct txlocator *locate_tx(const void *ctx, struct lightningd_state *dstate, + const struct sha256_double *txid) +{ + struct block *block = block_for_tx(dstate, txid); + if (block == NULL) { + return NULL; + } + + struct txlocator *loc = talz(ctx, struct txlocator); + loc->blkheight = block->height; + + size_t i, n = tal_count(block->txids); + for (i = 0; i < n; i++) { + if (structeq(&block->txids[i], txid)){ + loc->index = i; + break; + } + } + return loc; +} + void setup_topology(struct lightningd_state *dstate) { dstate->topology = tal(dstate, struct topology); diff --git a/daemon/chaintopology.h b/daemon/chaintopology.h index 815c8a110a4f..ac9e9f154479 100644 --- a/daemon/chaintopology.h +++ b/daemon/chaintopology.h @@ -10,6 +10,16 @@ struct peer; struct sha256_double; struct txwatch; +/* Information relevant to locating a TX in a blockchain. */ +struct txlocator { + + /* The height of the block that includes this transaction */ + u32 blkheight; + + /* Position of the transaction in the transactions list */ + u32 index; +}; + /* This is the number of blocks which would have to be mined to invalidate * the tx. */ size_t get_tx_depth(struct lightningd_state *dstate, @@ -33,4 +43,6 @@ void broadcast_tx(struct peer *peer, const struct bitcoin_tx *tx); void setup_topology(struct lightningd_state *dstate); +struct txlocator *locate_tx(const void *ctx, struct lightningd_state *dstate, const struct sha256_double *txid); + #endif /* LIGHTNING_DAEMON_CRYPTOPKT_H */ diff --git a/daemon/irc_announce.c b/daemon/irc_announce.c new file mode 100644 index 000000000000..1b2255aae731 --- /dev/null +++ b/daemon/irc_announce.c @@ -0,0 +1,155 @@ +#include "bitcoin/privkey.h" +#include "bitcoin/signature.h" +#include "daemon/chaintopology.h" +#include "daemon/irc_announce.h" +#include "daemon/lightningd.h" +#include "daemon/log.h" +#include "daemon/peer.h" +#include "daemon/routing.h" +#include "daemon/secrets.h" +#include "daemon/timeout.h" +#include "utils.h" + +#include +#include + +static bool announce_channel(const tal_t *ctx, struct ircstate *state, struct peer *p) +{ + char txid[65]; + int siglen; + u8 der[72]; + struct signature sig; + struct privmsg *msg = talz(ctx, struct privmsg); + struct txlocator *loc = locate_tx(ctx, state->dstate, &p->anchor.txid); + + if (loc == NULL) + return false; + + bitcoin_txid_to_hex(&p->anchor.txid, txid, sizeof(txid)); + msg->channel = "#lightning-nodes"; + msg->msg = tal_fmt( + msg, "CHAN %s %s %s %d %d %d %d %d", + pubkey_to_hexstr(msg, state->dstate->secpctx, &state->dstate->id), + pubkey_to_hexstr(msg, state->dstate->secpctx, p->id), + txid, + loc->blkheight, + loc->index, + state->dstate->config.fee_base, + state->dstate->config.fee_per_satoshi, + p->remote.locktime.locktime + ); + + privkey_sign(state->dstate, msg->msg, strlen(msg->msg), &sig); + siglen = signature_to_der(state->dstate->secpctx, der, &sig); + msg->msg = tal_fmt(msg, "%s %s", tal_hexstr(ctx, der, siglen), msg->msg); + + irc_send_msg(state, msg); + return true; +} + +static void announce_channels(struct ircstate *state) +{ + tal_t *ctx = tal(state, tal_t); + struct peer *p; + + list_for_each(&state->dstate->peers, p, list) { + + if (!state_is_normal(p->state)) + continue; + announce_channel(ctx, state, p); + } + tal_free(ctx); + + new_reltimer(state->dstate, state, time_from_sec(60), announce_channels, state); +} + +/* Reconnect to IRC server upon disconnection. */ +static void handle_irc_disconnect(struct ircstate *state) +{ + new_reltimer(state->dstate, state, state->reconnect_timeout, irc_connect, state); +} + +/* + * Handle an incoming message by checking if it is a channel + * announcement, parse it and add the channel to the topology if yes. + * + * The format for a valid announcement is: + * CHAN + * + */ +static void handle_irc_privmsg(struct ircstate *istate, const struct privmsg *msg) +{ + int blkheight; + char **splits = tal_strsplit(msg, msg->msg + 1, " ", STR_NO_EMPTY); + + if (tal_count(splits) != 11 || !streq(splits[1], "CHAN")) + return; + + int siglen = hex_data_size(strlen(splits[0])); + u8 *der = tal_hexdata(msg, splits[0], strlen(splits[0])); + if (der == NULL) + return; + + struct signature sig; + struct sha256_double hash; + char *content = strchr(msg->msg, ' ') + 1; + if (!signature_from_der(istate->dstate->secpctx, der, siglen, &sig)) + return; + + sha256_double(&hash, content, strlen(content)); + splits++; + + struct pubkey *pk1 = talz(msg, struct pubkey); + struct pubkey *pk2 = talz(msg, struct pubkey); + struct sha256_double *txid = talz(msg, struct sha256_double); + int index; + + bool ok = true; + ok &= pubkey_from_hexstr(istate->dstate->secpctx, splits[1], strlen(splits[1]), pk1); + ok &= pubkey_from_hexstr(istate->dstate->secpctx, splits[2], strlen(splits[2]), pk2); + ok &= bitcoin_txid_from_hex(splits[3], strlen(splits[3]), txid); + blkheight = atoi(splits[4]); + index = atoi(splits[5]); + if (!ok || index < 0 || blkheight < 0) { + log_debug(istate->dstate->base_log, "Unable to parse channel announcent."); + return; + } + + if (!check_signed_hash(istate->dstate->secpctx, &hash, &sig, pk1)) { + log_debug(istate->log, + "Ignoring announcement from %s, signature check failed.", + splits[1]); + return; + } + + /* + * FIXME Check in topology that the tx is in the block and + * that the endpoints match. + */ + + add_connection(istate->dstate, pk1, pk2, atoi(splits[6]), + atoi(splits[7]), atoi(splits[8]), 6); +} + +void setup_irc_connection(struct lightningd_state *dstate) +{ + // Register callback + irc_privmsg_cb = *handle_irc_privmsg; + irc_disconnect_cb = *handle_irc_disconnect; + + struct ircstate *state = talz(dstate, struct ircstate); + state->dstate = dstate; + state->server = "irc.freenode.net"; + state->reconnect_timeout = time_from_sec(15); + state->log = new_log(state, state->dstate->log_record, "%s:irc", + log_prefix(state->dstate->base_log)); + + /* Truncate nick at 13 bytes, would be imposed by freenode anyway */ + state->nick = tal_fmt( + state, + "N%.12s", + pubkey_to_hexstr(state, dstate->secpctx, &dstate->id) + 1); + + irc_connect(state); + announce_channels(state); +} diff --git a/daemon/irc_announce.h b/daemon/irc_announce.h new file mode 100644 index 000000000000..bce649771a5f --- /dev/null +++ b/daemon/irc_announce.h @@ -0,0 +1,9 @@ +#ifndef LIGHTNING_DAEMON_IRC_ANNOUNCE_H +#define LIGHTNING_DAEMON_IRC_ANNOUNCE_H +#include "config.h" +#include "irc.h" + +// Main entrypoint for the lightning daemon +void setup_irc_connection(struct lightningd_state *dstate); + +#endif /* LIGHTNING_DAEMON_IRC_ANNOUNCE_H */ diff --git a/daemon/lightningd.c b/daemon/lightningd.c index 8df13b2dd6d8..fef17f629e62 100644 --- a/daemon/lightningd.c +++ b/daemon/lightningd.c @@ -3,6 +3,7 @@ #include "configdir.h" #include "controlled_time.h" #include "db.h" +#include "irc_announce.h" #include "jsonrpc.h" #include "lightningd.h" #include "log.h" @@ -145,6 +146,9 @@ static void config_register_opts(struct lightningd_state *dstate) dstate, "Add route of form srcid/dstid/base/var/delay/minblocks" "(base in millisatoshi, var in millionths of satoshi per satoshi)"); + opt_register_noarg("--disable-irc", opt_set_invbool, + &dstate->config.use_irc, + "Disable IRC peer discovery for routing"); } static void dev_register_opts(struct lightningd_state *dstate) @@ -210,6 +214,9 @@ static void default_config(struct config *config) config->fee_base = 546000; /* Take 0.001% */ config->fee_per_satoshi = 10; + + /* Discover new peers using IRC */ + config->use_irc = true; } static void check_config(struct lightningd_state *dstate) @@ -360,6 +367,10 @@ int main(int argc, char *argv[]) /* Set up connections from peers. */ setup_listeners(dstate, portnum); + /* set up IRC peer discovery */ + if (dstate->config.use_irc) + setup_irc_connection(dstate); + /* Make sure we use the artificially-controlled time for timers */ io_time_override(controlled_time); diff --git a/daemon/lightningd.h b/daemon/lightningd.h index 88ccd4536931..dc8f8d1624c6 100644 --- a/daemon/lightningd.h +++ b/daemon/lightningd.h @@ -56,6 +56,9 @@ struct config { /* How long between changing commit and sending COMMIT message. */ struct timerel commit_time; + + /* Whether to enable IRC peer discovery. */ + bool use_irc; }; /* Here's where the global variables hide! */ diff --git a/irc.c b/irc.c new file mode 100644 index 000000000000..d1eebac1cf75 --- /dev/null +++ b/irc.c @@ -0,0 +1,178 @@ +#include "irc.h" +#include "daemon/dns.h" +#include "daemon/log.h" + +void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *) = NULL; +void (*irc_disconnect_cb)(struct ircstate *) = NULL; + +static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state); +static void irc_disconnected(struct io_conn *conn, struct ircstate *state); + +bool irc_send_msg(struct ircstate *state, struct privmsg *m) +{ + return irc_send(state, "PRIVMSG", "%s :%s", m->channel, m->msg); +} + +/* Send a raw irccommand to the IRC server. */ +bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...) +{ + va_list ap; + struct irccommand *c = tal(state, struct irccommand); + + c->prefix = NULL; + + if (!state->connected) + return false; + + va_start(ap, fmt); + c->command = tal_strdup(c, command); + c->params = tal_vfmt(c, fmt, ap); + va_end(ap); + + list_add_tail(&state->writequeue, &c->list); + io_wake(state); + return true; +} + +/* Write buffered irccommands to the IRC connection. Commands can be + buffered using irc_send. */ +static struct io_plan *irc_write_loop(struct io_conn *conn, struct ircstate *state) +{ + state->writebuffer = tal_free(state->writebuffer); + + struct irccommand *m = list_pop(&state->writequeue, struct irccommand, list); + if (m == NULL) + return io_out_wait(conn, state, irc_write_loop, state); + + bool hasprefix = m->prefix == NULL; + state->writebuffer = tal_fmt( + state, "%s%s%s %s\r\n", + hasprefix ? "" : m->prefix, + hasprefix ? "" : " ", + m->command, + m->params); + + tal_free(m); + + log_debug(state->log, "Sending: \"%s\"", state->writebuffer); + + return io_write( + conn, + state->writebuffer, strlen(state->writebuffer), + irc_write_loop, state + ); +} + +/* + * Called by the read loop to handle individual lines. This splits the + * line into a struct irccommand and passes it on to the specific + * handlers for the irccommand type. It silently drops any irccommand + * that has an unhandled type. + */ +static void handle_irc_command(struct ircstate *state, const char *line) +{ + log_debug(state->log, "Received: \"%s\"", line); + + struct irccommand *m = talz(state, struct irccommand); + char** splits = tal_strsplit(m, line, " ", STR_NO_EMPTY); + int numsplits = tal_count(splits) - 1; + + if (numsplits > 2 && strstarts(splits[0], ":")) { + m->prefix = splits[0]; + splits++; + } + m->command = splits[0]; + m->params = tal_strjoin(m, splits + 1, " ", STR_NO_TRAIL); + + if (streq(m->command, "PING")) { + irc_send(state, "PONG", "%s", m->params); + + } else if (streq(m->command, "PRIVMSG")) { + struct privmsg *pm = talz(m, struct privmsg); + pm->sender = m->prefix; + pm->channel = splits[1]; + pm->msg = tal_strjoin(m, splits + 2, " ", STR_NO_TRAIL); + irc_privmsg_cb(state, pm); + } + tal_free(m); +} + +/* + * Read incoming data and split it along the newline boundaries. Takes + * care of buffering incomplete lines and passes the lines to the + * handle_irc_command handler. + */ +static struct io_plan *irc_read_loop(struct io_conn *conn, struct ircstate *state) +{ + + size_t len = state->readlen + state->buffered; + char *start = state->buffer, *end; + + while ((end = memchr(start, '\n', len)) != NULL) { + /* Strip "\r\n" from lines. */ + const char *line = tal_strndup(state, start, end - 1 - start); + handle_irc_command(state, line); + tal_free(line); + len -= (end + 1 - start); + start = end + 1; + } + + /* Move any partial data back down. */ + memmove(state->buffer, start, len); + state->buffered = len; + + return io_read_partial(conn, state->buffer + state->buffered, + sizeof(state->buffer) - state->buffered, + &state->readlen, irc_read_loop, state); +} + +static void irc_failed(struct lightningd_state *dstate, struct ircstate *state) +{ + irc_disconnected(state->conn, state); + state->connected = false; +} + +static void irc_disconnected(struct io_conn *conn, struct ircstate *state) +{ + log_debug(state->log, "Lost connection to IRC server"); + state->connected = false; + state->conn = NULL; + state->readlen = 0; + state->buffered = 0; + memset(state->buffer, 0, sizeof(state->buffer)); + + /* Clear any pending commands, they're no longer useful */ + while (!list_empty(&state->writequeue)) + tal_free(list_pop(&state->writequeue, struct irccommand, list)); + + /* Same goes for partially written commands */ + state->writebuffer = tal_free(state->writebuffer); + + if (irc_disconnect_cb != NULL) + irc_disconnect_cb(state); +} + +void irc_connect(struct ircstate *state) +{ + state->connected = false; + list_head_init(&state->writequeue); + + log_debug(state->log, "Connecting to IRC server %s", state->server); + dns_resolve_and_connect(state->dstate, state->server, "6667", irc_connected, irc_failed, state); +} + +static struct io_plan *irc_connected(struct io_conn *conn, struct lightningd_state *dstate, struct ircstate *state) +{ + io_set_finish(conn, irc_disconnected, state); + state->conn = conn; + state->connected = true; + irc_send(state, "USER", "%s 0 * :A lightning node", state->nick); + irc_send(state, "NICK", "%s", state->nick); + irc_send(state, "JOIN", "#lightning-nodes"); + + return io_duplex(conn, + io_read_partial(conn, + state->buffer, sizeof(state->buffer), + &state->readlen, irc_read_loop, state), + irc_write_loop(conn, state)); +} diff --git a/irc.h b/irc.h new file mode 100644 index 000000000000..6d9cf5f46e3b --- /dev/null +++ b/irc.h @@ -0,0 +1,67 @@ +#ifndef LIGHTNING_IRC_H +#define LIGHTNING_IRC_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "daemon/lightningd.h" + +struct irccommand { + struct list_node list; + const char *prefix; + const char *command; + const char *params; +}; + +struct privmsg { + const char *channel; + const char *sender; + const char *msg; +}; + +struct ircstate { + /* Meta information */ + const char *nick; + const char *server; + + /* Connection and reading */ + struct io_conn *conn; + char buffer[512]; + size_t readlen; + size_t buffered; + + /* Write queue related */ + struct list_head writequeue; + char *writebuffer; + + /* Pointer to external state, making it available to callbacks */ + struct lightningd_state *dstate; + + struct log *log; + + /* Are we currently connected? */ + bool connected; + + /* Time to wait after getting disconnected before reconnecting. */ + struct timerel reconnect_timeout; +}; + +/* Callback to register for incoming messages */ +extern void (*irc_privmsg_cb)(struct ircstate *, const struct privmsg *); +extern void (*irc_disconnect_cb)(struct ircstate *); + +/* Send messages to IRC */ +bool irc_send(struct ircstate *state, const char *command, const char *fmt, ...) PRINTF_FMT(3,4); +bool irc_send_msg(struct ircstate *state, struct privmsg *m); + +/* Register IRC connection with io */ +void irc_connect(struct ircstate *state); + +#endif /* LIGHTNING_IRC_H */ diff --git a/utils.c b/utils.c index 86d9790d4316..4058a2bbfbdf 100644 --- a/utils.c +++ b/utils.c @@ -7,3 +7,11 @@ char *tal_hexstr(const tal_t *ctx, const void *data, size_t len) hex_encode(data, len, str, hex_str_size(len)); return str; } + +u8 *tal_hexdata(const tal_t *ctx, const void *str, size_t len) +{ + u8 *data = tal_arr(ctx, u8, hex_data_size(len)); + if (!hex_decode(str, len, data, hex_data_size(len))) + return NULL; + return data; +} diff --git a/utils.h b/utils.h index f89af8c957d8..0fac3c7c8bc9 100644 --- a/utils.h +++ b/utils.h @@ -1,9 +1,13 @@ #ifndef LIGHTNING_UTILS_H #define LIGHTNING_UTILS_H #include "config.h" +#include #include /* Allocate and fill in a hex-encoded string of this data. */ char *tal_hexstr(const tal_t *ctx, const void *data, size_t len); +/* Allocate and fill a buffer with the data of this hex string. */ +u8 *tal_hexdata(const tal_t *ctx, const void *str, size_t len); + #endif /* LIGHTNING_UTILS_H */