Skip to content

Commit ec989c7

Browse files
committed
add signmessagewithkey RPC
signmessagewithkey: allows to sign a message with a key associated with one bitcoin address in our wallet. Changelog-Added: add a new rpc command signmessagewithkey to sign input messages with keys from our wallet. Signed-off-by: Lagrang3 <lagrang3@protonmail.com>
1 parent 300f4fe commit ec989c7

File tree

3 files changed

+291
-0
lines changed

3 files changed

+291
-0
lines changed

contrib/msggen/msggen/schema.json

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32794,6 +32794,91 @@
3279432794
}
3279532795
]
3279632796
},
32797+
"lightning-signmessagewithkey.json": {
32798+
"$schema": "../rpc-schema-draft.json",
32799+
"type": "object",
32800+
"rpc": "signmessagewithkey",
32801+
"title": "Command to create a signature using a key from the wallet",
32802+
"description": [
32803+
"The **signmessagewithkey** RPC command creates a digital signature of *message* using the key associated with the address provided in the input.",
32804+
"The signature scheme follows the BIP137 specification."
32805+
],
32806+
"added": "v25.05",
32807+
"request": {
32808+
"required": [
32809+
"message",
32810+
"address"
32811+
],
32812+
"additionalProperties": false,
32813+
"properties": {
32814+
"message": {
32815+
"type": "string",
32816+
"description": [
32817+
"Less than 65536 characters long message to be signed by the node."
32818+
]
32819+
},
32820+
"address": {
32821+
"type": "string",
32822+
"description": [
32823+
"A Bitcoin accepted type address for lookup in the list of addresses issued to date.",
32824+
"Only P2WPKH type addresses are supported"
32825+
]
32826+
}
32827+
}
32828+
},
32829+
"response": {
32830+
"required": [
32831+
"address",
32832+
"keyidx",
32833+
"pubkey",
32834+
"signature",
32835+
"base64"
32836+
],
32837+
"additionalProperties": false,
32838+
"properties": {
32839+
"address": {
32840+
"type": "string",
32841+
"description": [
32842+
"The bitcoin address used for signing."
32843+
]
32844+
},
32845+
"keyidx": {
32846+
"type": "u64",
32847+
"description": [
32848+
"The key index of the address requested."
32849+
]
32850+
},
32851+
"pubkey": {
32852+
"type": "pubkey",
32853+
"description": [
32854+
"The public key associated with the bitcoin address provided."
32855+
]
32856+
},
32857+
"signature": {
32858+
"type": "hex",
32859+
"description": [
32860+
"The signature."
32861+
]
32862+
},
32863+
"base64": {
32864+
"type": "string",
32865+
"description": [
32866+
"The signature encoded in base64."
32867+
]
32868+
}
32869+
}
32870+
},
32871+
"author": [
32872+
"Lagrang3 <<lagrang3@protonmail.com>> is mainly responsible."
32873+
],
32874+
"see_also": [
32875+
"lightning-checkmessagewithkey(7)"
32876+
],
32877+
"resources": [
32878+
"Main web site: <https://github.com/ElementsProject/lightning>"
32879+
],
32880+
"examples": []
32881+
},
3279732882
"lightning-signpsbt.json": {
3279832883
"$schema": "../rpc-schema-draft.json",
3279932884
"type": "object",
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
{
2+
"$schema": "../rpc-schema-draft.json",
3+
"type": "object",
4+
"rpc": "signmessagewithkey",
5+
"title": "Command to create a signature using a key from the wallet",
6+
"description": [
7+
"The **signmessagewithkey** RPC command creates a digital signature of *message* using the key associated with the address provided in the input.",
8+
"The signature scheme follows the BIP137 specification."
9+
],
10+
"added": "v25.05",
11+
"request": {
12+
"required": [
13+
"message",
14+
"address"
15+
],
16+
"additionalProperties": false,
17+
"properties": {
18+
"message": {
19+
"type": "string",
20+
"description": [
21+
"Less than 65536 characters long message to be signed by the node."
22+
]
23+
},
24+
"address": {
25+
"type": "string",
26+
"description": [
27+
"A Bitcoin accepted type address for lookup in the list of addresses issued to date.",
28+
"Only P2WPKH type addresses are supported"
29+
]
30+
}
31+
}
32+
},
33+
"response": {
34+
"required": [
35+
"address",
36+
"keyidx",
37+
"pubkey",
38+
"signature",
39+
"base64"
40+
],
41+
"additionalProperties": false,
42+
"properties": {
43+
"address": {
44+
"type": "string",
45+
"description": [
46+
"The bitcoin address used for signing."
47+
]
48+
},
49+
"keyidx": {
50+
"type": "u64",
51+
"description": [
52+
"The key index of the address requested."
53+
]
54+
},
55+
"pubkey": {
56+
"type": "pubkey",
57+
"description": [
58+
"The public key associated with the bitcoin address provided."
59+
]
60+
},
61+
"signature": {
62+
"type": "hex",
63+
"description": [
64+
"The signature."
65+
]
66+
},
67+
"base64": {
68+
"type": "string",
69+
"description": [
70+
"The signature encoded in base64."
71+
]
72+
}
73+
}
74+
},
75+
"author": [
76+
"Lagrang3 <<lagrang3@protonmail.com>> is mainly responsible."
77+
],
78+
"see_also": [
79+
"lightning-checkmessagewithkey(7)"
80+
],
81+
"resources": [
82+
"Main web site: <https://github.com/ElementsProject/lightning>"
83+
],
84+
"examples": [
85+
]
86+
}
87+

wallet/walletrpc.c

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include <ccan/array_size/array_size.h>
55
#include <ccan/cast/cast.h>
66
#include <common/addr.h>
7+
#include <common/base64.h>
78
#include <common/bech32.h>
89
#include <common/configdir.h>
910
#include <common/json_command.h>
@@ -1066,3 +1067,121 @@ static const struct json_command sendpsbt_command = {
10661067
};
10671068

10681069
AUTODATA(json_command, &sendpsbt_command);
1070+
1071+
static struct command_result *
1072+
json_signmessagewithkey(struct command *cmd, const char *buffer,
1073+
const jsmntok_t *obj UNNEEDED, const jsmntok_t *params)
1074+
{
1075+
/* decoding the address */
1076+
const u8 *scriptpubkey;
1077+
const char *message;
1078+
1079+
/* from wallet BIP32 */
1080+
struct pubkey pubkey;
1081+
1082+
if (!param(
1083+
cmd, buffer, params,
1084+
p_req("message", param_string, &message),
1085+
p_req("address", param_bitcoin_address, &scriptpubkey),
1086+
NULL))
1087+
return command_param_failed();
1088+
1089+
const size_t script_len = tal_bytelen(scriptpubkey);
1090+
1091+
/* FIXME: we already had the address from the input */
1092+
char *addr;
1093+
addr = encode_scriptpubkey_to_addr(tmpctx, chainparams, scriptpubkey);
1094+
1095+
if (!is_p2wpkh(scriptpubkey, script_len, NULL)) {
1096+
/* FIXME add support for BIP 322 */
1097+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
1098+
"address %s is not p2wpkh and "
1099+
"it is not supported for signing",
1100+
addr);
1101+
}
1102+
1103+
u32 keyidx;
1104+
1105+
/* loop over all generated keys, find a matching key */
1106+
/* FIXME: alternatively, can we use the our_addresses hash table?
1107+
*struct script_with_len *key;
1108+
*struct wallet_address *addr =
1109+
* wallet_address_htable_get(cmd->ld->wallet->our_addresses, key);
1110+
*/
1111+
struct issued_address_type *listaddrtypes =
1112+
wallet_list_addresses(tmpctx, cmd->ld->wallet, 1, NULL);
1113+
for (size_t i = 0; i < tal_count(listaddrtypes); i++) {
1114+
if (listaddrtypes[i].keyidx == BIP32_INITIAL_HARDENED_CHILD) {
1115+
break;
1116+
}
1117+
bip32_pubkey(cmd->ld, &pubkey, listaddrtypes[i].keyidx);
1118+
char *out_p2wpkh = "";
1119+
char *out_p2tr = "";
1120+
if (listaddrtypes[i].addrtype == ADDR_BECH32 ||
1121+
listaddrtypes[i].addrtype == ADDR_ALL) {
1122+
u8 *redeemscript_p2wpkh;
1123+
out_p2wpkh = encode_pubkey_to_addr(
1124+
cmd, &pubkey, ADDR_BECH32, &redeemscript_p2wpkh);
1125+
if (!out_p2wpkh) {
1126+
abort();
1127+
}
1128+
}
1129+
if (listaddrtypes[i].addrtype == ADDR_P2TR ||
1130+
listaddrtypes[i].addrtype == ADDR_ALL) {
1131+
out_p2tr =
1132+
encode_pubkey_to_addr(cmd, &pubkey, ADDR_P2TR,
1133+
/* out_redeemscript */ NULL);
1134+
if (!out_p2tr) {
1135+
abort();
1136+
}
1137+
}
1138+
1139+
if (streq(addr, out_p2wpkh) || streq(addr, out_p2tr)) {
1140+
keyidx = listaddrtypes[i].keyidx;
1141+
break;
1142+
}
1143+
}
1144+
1145+
/* wire to hsmd a sign request */
1146+
u8 *msg = towire_hsmd_sign_message_with_key(
1147+
cmd, tal_dup_arr(tmpctx, u8, (u8 *)message, strlen(message), 0),
1148+
keyidx);
1149+
if (!wire_sync_write(cmd->ld->hsm_fd, take(msg))) {
1150+
fatal("Could not write sign_with_key to HSM: %s",
1151+
strerror(errno));
1152+
}
1153+
1154+
/* read form hsmd a sign reply */
1155+
msg = wire_sync_read(cmd, cmd->ld->hsm_fd);
1156+
1157+
int recid;
1158+
u8 sig[65];
1159+
secp256k1_ecdsa_recoverable_signature rsig;
1160+
1161+
if (!fromwire_hsmd_sign_message_with_key_reply(msg, &rsig)) {
1162+
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
1163+
"HSM gave bad sign_with_key_reply %s",
1164+
tal_hex(tmpctx, msg));
1165+
}
1166+
1167+
secp256k1_ecdsa_recoverable_signature_serialize_compact(
1168+
secp256k1_ctx, sig + 1, &recid, &rsig);
1169+
/* this is the header value for P2WPKH specified in BIP137 */
1170+
sig[0] = recid + 39;
1171+
1172+
struct json_stream *response;
1173+
response = json_stream_success(cmd);
1174+
json_add_string(response, "address", addr);
1175+
json_add_u32(response, "keyidx", keyidx);
1176+
json_add_pubkey(response, "pubkey", &pubkey);
1177+
json_add_hex(response, "signature", sig, sizeof(sig));
1178+
json_add_string(response, "base64",
1179+
b64_encode(tmpctx, sig, sizeof(sig)));
1180+
return command_success(cmd, response);
1181+
}
1182+
1183+
static const struct json_command signmessagewithkey_command = {
1184+
"signmessagewithkey",
1185+
json_signmessagewithkey
1186+
};
1187+
AUTODATA(json_command, &signmessagewithkey_command);

0 commit comments

Comments
 (0)