Files
lightning/lightningd/signmessage.c
Vincenzo Palazzo 1d671a2380 rpc: checkmessage return an error if pubkey is not found
Returning an warning message when the pub key is not specified and there is no node in the graph.

We try to help people that use core lightning as a signer and nothings else.

Changelog-Deprecated: rpc: checkmessage return an error when the pubkey is not specified and it is unknown in the network graph.
2022-07-19 17:55:31 +02:00

241 lines
8.6 KiB
C

#include "config.h"
#include <common/bech32.h>
#include <common/configdir.h>
#include <common/json_command.h>
#include <common/json_param.h>
#include <errno.h>
#include <hsmd/hsmd_wiregen.h>
#include <lightningd/plugin.h>
#include <wire/wire_sync.h>
/* These tables copied from zbase32 src:
* copyright 2002-2007 Zooko "Zooko" Wilcox-O'Hearn
* mailto:zooko@zooko.com
*
* Permission is hereby granted to any person obtaining a copy of this work to
* deal in this work without restriction (including the rights to use, modify,
* distribute, sublicense, and/or sell copies).
*/
static const char*const zbase32_chars="ybndrfg8ejkmcpqxot1uwisza345h769";
/* revchars: index into this table with the ASCII value of the char. The result is the value of that quintet. */
static const u8 zbase32_revchars[]={ 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 18, 255, 25, 26, 27, 30, 29, 7, 31, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 24, 1, 12, 3, 8, 5, 6, 28, 21, 9, 10, 255, 11, 2, 16, 13, 14, 4, 22, 17, 19, 255, 20, 15, 0, 23, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, };
static const char *to_zbase32(const tal_t *ctx, const u8 *msg, size_t srclen)
{
size_t outlen;
char *out = tal_arr(ctx, char, (srclen * 8 + 4) / 5 + 1);
outlen = 0;
if (!bech32_convert_bits((uint8_t *)out, &outlen, 5, msg, srclen, 8, true))
return tal_free(out);
assert(outlen < tal_bytelen(out));
for (size_t i = 0; i < outlen; i++)
out[i] = zbase32_chars[(unsigned)out[i]];
out[outlen] = '\0';
return out;
}
static const u8 *from_zbase32(const tal_t *ctx, const char *msg)
{
u5 *u5arr;
u8 *u8arr;
size_t len;
u5arr = tal_arr(tmpctx, u5, strlen(msg));
for (size_t i = 0; i < tal_bytelen(u5arr); i++) {
u5arr[i] = zbase32_revchars[(unsigned char)msg[i]];
if (u5arr[i] > 31)
return NULL;
}
u8arr = tal_arr(ctx, u8, (tal_bytelen(u5arr) * 5 + 7) / 8);
len = 0;
if (!bech32_convert_bits(u8arr, &len, 8,
u5arr, tal_bytelen(u5arr), 5, false))
return tal_free(u8arr);
return len == tal_bytelen(u8arr) ? u8arr : tal_free(u8arr);
}
static struct command_result *json_signmessage(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
const char *message;
secp256k1_ecdsa_recoverable_signature rsig;
struct json_stream *response;
u8 sig[65], *msg;
int recid;
if (!param(cmd, buffer, params,
p_req("message", param_string, &message),
NULL))
return command_param_failed();
if (strlen(message) > 65535)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Message must be < 64k");
msg = towire_hsmd_sign_message(NULL,
tal_dup_arr(tmpctx, u8, (u8 *)message,
strlen(message), 0));
if (!wire_sync_write(cmd->ld->hsm_fd, take(msg)))
fatal("Could not write to HSM: %s", strerror(errno));
msg = wire_sync_read(tmpctx, cmd->ld->hsm_fd);
if (!fromwire_hsmd_sign_message_reply(msg, &rsig))
fatal("HSM gave bad hsm_sign_message_reply %s",
tal_hex(msg, msg));
secp256k1_ecdsa_recoverable_signature_serialize_compact(secp256k1_ctx,
sig+1, &recid,
&rsig);
response = json_stream_success(cmd);
json_add_hex(response, "signature", sig+1, sizeof(sig)-1);
sig[0] = recid;
json_add_hex(response, "recid", sig, 1);
/* From https://twitter.com/rusty_twit/status/1182102005914800128:
* @roasbeef & @bitconner point out that #lnd algo is:
* zbase32(SigRec(SHA256(SHA256("Lightning Signed Message:" + msg)))).
* zbase32 from https://philzimmermann.com/docs/human-oriented-base-32-encoding.txt
* and SigRec has first byte 31 + recovery id, followed by 64 byte sig.
* #specinatweet */
sig[0] += 31;
json_add_string(response, "zbase",
to_zbase32(response, sig, sizeof(sig)));
return command_success(cmd, response);
}
static const struct json_command json_signmessage_cmd = {
"signmessage",
"utility",
json_signmessage,
"Create a digital signature of {message}",
};
AUTODATA(json_command, &json_signmessage_cmd);
struct command_and_node {
struct command *cmd;
struct node_id id;
};
/* topology tells us if it's a known node by returning details. */
static void listnodes_done(const char *buffer,
const jsmntok_t *toks,
const jsmntok_t *idtok UNUSED,
struct command_and_node *can)
{
struct json_stream *response;
const jsmntok_t *t;
t = json_get_member(buffer, toks, "result");
if (t)
t = json_get_member(buffer, t, "nodes");
if (!deprecated_apis && (!t || t->size == 0)) {
was_pending(command_fail(can->cmd, SIGNMESSAGE_PUBKEY_NOT_FOUND,
"pub key not found in the graph, expected pubkey is %s",
node_id_to_hexstr(tmpctx, &can->id)));
return;
}
response = json_stream_success(can->cmd);
json_add_node_id(response, "pubkey", &can->id);
json_add_bool(response, "verified", t && t->size == 1);
was_pending(command_success(can->cmd, response));
}
static struct command_result *json_checkmessage(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
struct pubkey *pubkey, reckey;
const u8 *u8sig;
const char *message, *zb;
secp256k1_ecdsa_recoverable_signature rsig;
struct sha256_ctx sctx = SHA256_INIT;
struct sha256_double shad;
struct json_stream *response;
if (!param(cmd, buffer, params,
p_req("message", param_string, &message),
p_req("zbase", param_string, &zb),
p_opt("pubkey", param_pubkey, &pubkey),
NULL))
return command_param_failed();
u8sig = from_zbase32(tmpctx, zb);
if (!u8sig)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"zbase is not valid zbase32");
if (tal_bytelen(u8sig) != 65)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"zbase is too %s",
tal_bytelen(u8sig) < 65 ? "short" : "long");
if (!secp256k1_ecdsa_recoverable_signature_parse_compact(secp256k1_ctx,
&rsig,
u8sig + 1,
u8sig[0] - 31))
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"cannot parse zbase signature");
sha256_update(&sctx, "Lightning Signed Message:",
strlen("Lightning Signed Message:"));
sha256_update(&sctx, message, strlen(message));
sha256_double_done(&sctx, &shad);
if (!secp256k1_ecdsa_recover(secp256k1_ctx, &reckey.pubkey, &rsig,
shad.sha.u.u8)) {
response = json_stream_success(cmd);
json_add_bool(response, "verified", false);
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) {
struct jsonrpc_request *req;
struct plugin *plugin;
struct command_and_node *can = tal(cmd, struct command_and_node);
node_id_from_pubkey(&can->id, &reckey);
can->cmd = cmd;
req = jsonrpc_request_start(cmd, "listnodes",
cmd->ld->log,
NULL, listnodes_done,
can);
json_add_node_id(req->stream, "id", &can->id);
jsonrpc_request_end(req);
/* Only works if we have listnodes! */
plugin = find_plugin_for_command(cmd->ld, "listnodes");
if (plugin) {
plugin_request_send(plugin, req);
return command_still_pending(cmd);
}
}
response = json_stream_success(cmd);
json_add_pubkey(response, "pubkey", &reckey);
json_add_bool(response, "verified",
pubkey && pubkey_eq(pubkey, &reckey));
return command_success(cmd, response);
}
static const struct json_command json_checkmessage_cmd = {
"checkmessage",
"utility",
json_checkmessage,
"Verify a digital signature {zbase} of {message} signed with {pubkey}",
};
AUTODATA(json_command, &json_checkmessage_cmd);