diff --git a/CHANGELOG.md b/CHANGELOG.md index 64e5c433d..f8c9299d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,7 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - JSON API: `listfunds` now lists a blockheight for confirmed transactions, and has `connected` and `state` fields for channels, like `listpeers`. - JSON API: `fundchannel_start` now includes field `scriptpubkey` - JSON API: New method `listtransactions` -- JSON API: `signmessage` will now create a signature from your node on a message. +- JSON API: `signmessage` will now create a signature from your node on a message; `checkmessage` will verify it. - Plugin: new notifications `sendpay_success` and `sendpay_failure`. - Protocol: nodes now announce features in `node_announcement` broadcasts. - Protocol: we now offer `option_gossip_queries_ex` for finegrained gossip control. diff --git a/lightningd/signmessage.c b/lightningd/signmessage.c index 860cffab7..4e395cceb 100644 --- a/lightningd/signmessage.c +++ b/lightningd/signmessage.c @@ -1,4 +1,5 @@ #include +#include #include #include #include @@ -20,6 +21,9 @@ */ 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; @@ -35,6 +39,28 @@ static const char *to_zbase32(const tal_t *ctx, const u8 *msg, size_t srclen) 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); + assert(len == tal_bytelen(u8arr)); + return u8arr; +} + static struct command_result *json_signmessage(struct command *cmd, const char *buffer, const jsmntok_t *obj UNNEEDED, @@ -94,3 +120,63 @@ static const struct json_command json_signmessage_cmd = { }; AUTODATA(json_command, &json_signmessage_cmd); +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_req("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); + + response = json_stream_success(cmd); + if (!secp256k1_ecdsa_recover(secp256k1_ctx, &reckey.pubkey, &rsig, + shad.sha.u.u8)) { + json_add_bool(response, "verified", false); + } else { + json_add_bool(response, "verified", 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); +