core-lightning/daemon/irc_announce.c

282 lines
7.9 KiB
C
Raw Normal View History

#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 <ccan/list/list.h>
#include <ccan/str/hex/hex.h>
/* Sign a privmsg by prepending the signature to the message */
static void sign_privmsg(struct ircstate *state, struct privmsg *msg)
{
int siglen;
u8 der[72];
secp256k1_ecdsa_signature sig;
privkey_sign(state->dstate, msg->msg, strlen(msg->msg), &sig);
siglen = signature_to_der(der, &sig);
msg->msg = tal_fmt(msg, "%s %s", tal_hexstr(msg, der, siglen), msg->msg);
}
static bool announce_channel(const tal_t *ctx, struct ircstate *state, struct peer *p)
{
char txid[65];
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->id),
pubkey_to_hexstr(msg, p->id),
txid,
loc->blkheight,
loc->index,
state->dstate->config.fee_base,
state->dstate->config.fee_per_satoshi,
state->dstate->config.min_htlc_expiry
);
sign_privmsg(state, msg);
irc_send_msg(state, msg);
return true;
}
/* Send an announcement for this node to the channel, including its
* hostname, port and ID */
static void announce_node(const tal_t *ctx, struct ircstate *state)
{
char *hostname = state->dstate->external_ip;
int port = state->dstate->portnum;
struct privmsg *msg = talz(ctx, struct privmsg);
if (hostname == NULL) {
//FIXME: log that we don't know our IP yet.
return;
}
msg->channel = "#lightning-nodes";
msg->msg = tal_fmt(
msg, "NODE %s %s %d",
pubkey_to_hexstr(msg, &state->dstate->id),
hostname,
port
);
sign_privmsg(state, msg);
irc_send_msg(state, msg);
}
/* Announce the node's contact information and all of its channels */
static void announce(struct ircstate *state)
{
tal_t *ctx = tal(state, tal_t);
struct peer *p;
announce_node(ctx, state);
list_for_each(&state->dstate->peers, p, list) {
if (!state_is_normal(p->state))
continue;
announce_channel(ctx, state, p);
}
tal_free(ctx);
/* By default we announce every 6 hours, otherwise when someone joins */
log_debug(state->log, "Setting long announce time: 6 hours");
state->dstate->announce = new_reltimer(state->dstate, state,
time_from_sec(3600 * 6),
announce, state);
}
/* Reconnect to IRC server upon disconnection. */
static void handle_irc_disconnect(struct ircstate *state)
{
/* Stop announcing. */
state->dstate->announce = tal_free(state->dstate->announce);
new_reltimer(state->dstate, state, state->reconnect_timeout, irc_connect, state);
}
/* Verify a signed privmsg */
static bool verify_signed_privmsg(
struct ircstate *istate,
const struct pubkey *pk,
const struct privmsg *msg)
{
secp256k1_ecdsa_signature sig;
struct sha256_double hash;
const char *m = msg->msg + 1;
int siglen = strchr(m, ' ') - m;
const char *content = m + siglen + 1;
u8 *der = tal_hexdata(msg, m, siglen);
siglen = hex_data_size(siglen);
if (der == NULL)
return false;
if (!signature_from_der(der, siglen, &sig))
return false;
sha256_double(&hash, content, strlen(content));
return check_signed_hash(&hash, &sig, pk);
}
static void handle_channel_announcement(
struct ircstate *istate,
const struct privmsg *msg,
char **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;
int blkheight;
ok &= pubkey_from_hexstr(splits[1], strlen(splits[1]), pk1);
ok &= pubkey_from_hexstr(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 (!verify_signed_privmsg(istate, pk1, msg)) {
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);
}
static void handle_node_announcement(
struct ircstate *istate,
const struct privmsg *msg,
char **splits)
{
struct pubkey *pk = talz(msg, struct pubkey);
char *hostname = tal_strdup(msg, splits[2]);
int port = atoi(splits[3]);
if (!pubkey_from_hexstr(splits[1], strlen(splits[1]), pk) || port < 1)
return;
if (!verify_signed_privmsg(istate, pk, msg)) {
log_debug(istate->log, "Ignoring node announcement from %s, signature check failed.",
splits[1]);
return;
} else if(splits[4] != NULL && strlen(splits[4]) > 64) {
log_debug(istate->log, "Ignoring node announcement from %s, alias too long",
splits[1]);
}
struct node *node = add_node(istate->dstate, pk, hostname, port);
if (splits[4] != NULL){
tal_free(node->alias);
node->alias = tal_hexdata(node, splits[4], strlen(splits[4]));
}
}
/*
* 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:
* <sig> CHAN <pk1> <pk2> <anchor txid> <block height> <tx position> <base_fee>
* <proportional_fee> <locktime>
*/
static void handle_irc_privmsg(struct ircstate *istate, const struct privmsg *msg)
{
char **splits = tal_strsplit(msg, msg->msg + 1, " ", STR_NO_EMPTY);
int splitcount = tal_count(splits) - 1;
if (splitcount < 2)
return;
char *type = splits[1];
if (splitcount == 10 && streq(type, "CHAN"))
handle_channel_announcement(istate, msg, splits + 1);
else if (splitcount >= 5 && streq(type, "NODE"))
handle_node_announcement(istate, msg, splits + 1);
}
static void handle_irc_command(struct ircstate *istate, const struct irccommand *cmd)
{
struct lightningd_state *dstate = istate->dstate;
char **params = tal_strsplit(cmd, cmd->params, " ", STR_NO_EMPTY);
if (streq(cmd->command, "338") && tal_count(params) >= 4) {
dstate->external_ip = tal_strdup(
istate->dstate, params[3]);
log_debug(dstate->base_log, "Detected my own IP as %s", dstate->external_ip);
// Add our node to the node_map for completeness
add_node(istate->dstate, &dstate->id,
dstate->external_ip, dstate->portnum);
} else if (streq(cmd->command, "JOIN")) {
unsigned int delay;
/* Throw away any existing announce timer, and announce within
* 60 seconds. */
dstate->announce = tal_free(dstate->announce);
delay = pseudorand(60000000);
log_debug(istate->log, "Setting new announce time %u sec",
delay / 1000000);
dstate->announce = new_reltimer(dstate, istate,
time_from_usec(delay),
announce, istate);
}
}
static void handle_irc_connected(struct ircstate *istate)
{
irc_send(istate, "JOIN", "#lightning-nodes");
irc_send(istate, "WHOIS", "%s", istate->nick);
}
void setup_irc_connection(struct lightningd_state *dstate)
{
// Register callback
irc_privmsg_cb = *handle_irc_privmsg;
irc_connect_cb = *handle_irc_connected;
irc_disconnect_cb = *handle_irc_disconnect;
irc_command_cb = *handle_irc_command;
struct ircstate *state = talz(dstate, struct ircstate);
state->dstate = dstate;
state->server = "irc.lfnet.org";
state->reconnect_timeout = time_from_sec(15);
state->log = new_log(state, state->dstate->log_book, "%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->id) + 1);
/* We will see our own JOIN message, which will trigger announce */
irc_connect(state);
}