Browse Source

lightningd: checkmessage can intuit pubkey in some cases.

*If* we know the key has signed something else (as is the case for
channel_announcement) then we can effectively trust the key derivation.

This matches how LND's VerifyMessage works, though in the next patch
we will document it exactly.

Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>
travis-debug
Rusty Russell 5 years ago
committed by neil saitug
parent
commit
e5d9c7effc
  1. 56
      lightningd/signmessage.c
  2. 15
      tests/test_misc.py

56
lightningd/signmessage.c

@ -8,6 +8,7 @@
#include <errno.h> #include <errno.h>
#include <gossipd/gen_gossip_wire.h> #include <gossipd/gen_gossip_wire.h>
#include <hsmd/gen_hsm_wire.h> #include <hsmd/gen_hsm_wire.h>
#include <lightningd/subd.h>
#include <string.h> #include <string.h>
#include <wire/wire_sync.h> #include <wire/wire_sync.h>
@ -120,6 +121,32 @@ static const struct json_command json_signmessage_cmd = {
}; };
AUTODATA(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, static struct command_result *json_checkmessage(struct command *cmd,
const char *buffer, const char *buffer,
const jsmntok_t *obj UNNEEDED, const jsmntok_t *obj UNNEEDED,
@ -136,7 +163,7 @@ static struct command_result *json_checkmessage(struct command *cmd,
if (!param(cmd, buffer, params, if (!param(cmd, buffer, params,
p_req("message", param_string, &message), p_req("message", param_string, &message),
p_req("zbase", param_string, &zb), p_req("zbase", param_string, &zb),
p_req("pubkey", param_pubkey, &pubkey), p_opt("pubkey", param_pubkey, &pubkey),
NULL)) NULL))
return command_param_failed(); return command_param_failed();
@ -162,13 +189,34 @@ static struct command_result *json_checkmessage(struct command *cmd,
sha256_update(&sctx, message, strlen(message)); sha256_update(&sctx, message, strlen(message));
sha256_double_done(&sctx, &shad); sha256_double_done(&sctx, &shad);
response = json_stream_success(cmd);
if (!secp256k1_ecdsa_recover(secp256k1_ctx, &reckey.pubkey, &rsig, if (!secp256k1_ecdsa_recover(secp256k1_ctx, &reckey.pubkey, &rsig,
shad.sha.u.u8)) { shad.sha.u.u8)) {
response = json_stream_success(cmd);
json_add_bool(response, "verified", false); json_add_bool(response, "verified", false);
} else { return command_success(cmd, response);
json_add_bool(response, "verified", pubkey_eq(pubkey, &reckey));
} }
/* 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); return command_success(cmd, response);
} }

15
tests/test_misc.py

@ -1649,7 +1649,7 @@ def test_relative_config_dir(node_factory):
def test_signmessage(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, corpus = [[None,
"this is a test!", "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 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'] 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']

Loading…
Cancel
Save