diff --git a/lightningd/signmessage.c b/lightningd/signmessage.c index 4e395cceb..e0e9f58b4 100644 --- a/lightningd/signmessage.c +++ b/lightningd/signmessage.c @@ -8,6 +8,7 @@ #include #include #include +#include #include #include @@ -120,6 +121,32 @@ static const struct json_command json_signmessage_cmd = { }; AUTODATA(json_command, &json_signmessage_cmd); +struct command_and_node { + struct command *cmd; + struct node_id id; +}; + +/* Gossipd tells us if it's a known node by returning details. */ +static void getnode_reply(struct subd *gossip UNUSED, const u8 *reply, + const int *fds UNUSED, + struct command_and_node *can) +{ + struct gossip_getnodes_entry **nodes; + struct json_stream *response; + + if (!fromwire_gossip_getnodes_reply(reply, reply, &nodes)) { + log_broken(can->cmd->ld->log, + "Malformed gossip_getnodes response %s", + tal_hex(tmpctx, reply)); + nodes = NULL; + } + + response = json_stream_success(can->cmd); + json_add_node_id(response, "pubkey", &can->id); + json_add_bool(response, "verified", tal_count(nodes) > 0); + was_pending(command_success(can->cmd, response)); +} + static struct command_result *json_checkmessage(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -136,7 +163,7 @@ static struct command_result *json_checkmessage(struct command *cmd, if (!param(cmd, buffer, params, p_req("message", param_string, &message), p_req("zbase", param_string, &zb), - p_req("pubkey", param_pubkey, &pubkey), + p_opt("pubkey", param_pubkey, &pubkey), NULL)) return command_param_failed(); @@ -162,13 +189,34 @@ static struct command_result *json_checkmessage(struct command *cmd, sha256_update(&sctx, message, strlen(message)); sha256_double_done(&sctx, &shad); - response = json_stream_success(cmd); if (!secp256k1_ecdsa_recover(secp256k1_ctx, &reckey.pubkey, &rsig, shad.sha.u.u8)) { + response = json_stream_success(cmd); json_add_bool(response, "verified", false); - } else { - json_add_bool(response, "verified", pubkey_eq(pubkey, &reckey)); + return command_success(cmd, response); } + + /* If they didn't specify pubkey, we only accept the signature if it's + * in the graph (thus, they've signed something with it). This idea + * was stolen directly from lnd, thanks @roasbeef. + * + * FIXME: We could also look through known invoices: AFAICT you can't + * make two (different) signed messages with the same recovered key + * unless you know the secret key */ + if (!pubkey) { + u8 *req; + struct command_and_node *can = tal(cmd, struct command_and_node); + + node_id_from_pubkey(&can->id, &reckey); + can->cmd = cmd; + req = towire_gossip_getnodes_request(cmd, &can->id); + subd_req(cmd, cmd->ld->gossip, req, -1, 0, getnode_reply, can); + return command_still_pending(cmd); + } + + response = json_stream_success(cmd); + json_add_pubkey(response, "pubkey", &reckey); + json_add_bool(response, "verified", pubkey_eq(pubkey, &reckey)); return command_success(cmd, response); } diff --git a/tests/test_misc.py b/tests/test_misc.py index c2176da89..1f0fdfe0d 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -1649,7 +1649,7 @@ def test_relative_config_dir(node_factory): def test_signmessage(node_factory): - l1 = node_factory.get_node() + l1, l2 = node_factory.line_graph(2, wait_for_announce=True) corpus = [[None, "this is a test!", @@ -1685,3 +1685,16 @@ def test_signmessage(node_factory): assert l1.rpc.call('checkmessage', [c[1], c[2], c[3]])['verified'] assert not l1.rpc.call('checkmessage', [c[1] + "modified", c[2], c[3]])['verified'] + checknokey = l1.rpc.call('checkmessage', [c[1], c[2]]) + # Of course, we know our own pubkey + if c[3] == l1.info['id']: + assert checknokey['verified'] + else: + assert not checknokey['verified'] + assert checknokey['pubkey'] == c[3] + + # l2 knows about l1, so it can validate it. + zm = l1.rpc.call('signmessage', ["message for you"])['zbase'] + checknokey = l2.rpc.call('checkmessage', ["message for you", zm]) + assert checknokey['pubkey'] == l1.info['id'] + assert checknokey['verified']