Files
palladum-lightning/lightningd/hsm_control.c
Sangbida Chaudhuri 32bb0ed20e hsmd_wire: remove hsmd_derive_bip86_key and add it to hsmd_init_reply_v4
Instead of having a separate field to derive the bip86 base key, we return it in the hsmd init reply once we know that the hsm_secret is of mnemonic type
2025-10-26 12:37:58 +10:30

369 lines
12 KiB
C

#include "config.h"
#include <ccan/err/err.h>
#include <ccan/fdpass/fdpass.h>
#include <ccan/tal/str/str.h>
#include <common/bolt12_id.h>
#include <common/errcode.h>
#include <common/hsm_capable.h>
#include <common/hsm_secret.h>
#include <common/hsm_version.h>
#include <common/json_command.h>
#include <errno.h>
#include <hsmd/hsmd_wiregen.h>
#include <lightningd/hsm_control.h>
#include <lightningd/jsonrpc.h>
#include <lightningd/lightningd.h>
#include <lightningd/subd.h>
#include <wally_bip32.h>
#include <wire/wire_sync.h>
static int hsm_get_fd(struct lightningd *ld,
const struct node_id *id,
u64 dbid,
u64 permissions)
{
const u8 *msg;
msg = towire_hsmd_client_hsmfd(NULL, id, dbid, permissions);
msg = hsm_sync_req(tmpctx, ld, take(msg));
if (!fromwire_hsmd_client_hsmfd_reply(msg))
fatal("Bad reply from HSM: %s", tal_hex(tmpctx, msg));
return fdpass_recv(ld->hsm_fd);
}
int hsm_get_client_fd(struct lightningd *ld,
const struct node_id *id,
u64 dbid,
u64 permissions)
{
assert(dbid);
return hsm_get_fd(ld, id, dbid, permissions);
}
int hsm_get_global_fd(struct lightningd *ld, u64 permissions)
{
int fd = hsm_get_fd(ld, &ld->our_nodeid, 0, permissions);
if (fd < 0)
fatal("Could not read fd from HSM: %s", strerror(errno));
return fd;
}
static unsigned int hsm_msg(struct subd *hsmd,
const u8 *msg, const int *fds UNUSED)
{
/* We only expect one thing from the HSM that's not a STATUS message */
struct node_id client_id;
u8 *bad_msg;
char *desc;
if (!fromwire_hsmstatus_client_bad_request(tmpctx, msg, &client_id,
&desc, &bad_msg))
fatal("Bad status message from hsmd: %s", tal_hex(tmpctx, msg));
/* This should, of course, never happen. */
log_broken(hsmd->log, "client %s %s (request %s)",
fmt_node_id(tmpctx, &client_id),
desc, tal_hex(tmpctx, bad_msg));
return 0;
}
/* Is this capability supported by the HSM? (So far, always a message
* number) */
bool hsm_capable(struct lightningd *ld, u32 msgtype)
{
return hsm_is_capable(ld->hsm_capabilities, msgtype);
}
struct ext_key *hsm_init(struct lightningd *ld)
{
u8 *msg;
int fds[2];
struct ext_key *bip32_base;
u32 hsm_version;
struct pubkey unused;
/* We actually send requests synchronously: only status is async. */
if (socketpair(AF_LOCAL, SOCK_STREAM, 0, fds) != 0)
err(EXITCODE_HSM_GENERIC_ERROR, "Could not create hsm socketpair");
ld->hsm = new_global_subd(ld, "lightning_hsmd",
hsmd_wire_name,
hsm_msg,
take(&fds[1]), NULL);
if (!ld->hsm)
err(EXITCODE_HSM_GENERIC_ERROR, "Could not subd hsm");
ld->hsm_fd = fds[0];
if (ld->developer) {
struct tlv_hsmd_dev_preinit_tlvs *tlv;
tlv = tlv_hsmd_dev_preinit_tlvs_new(tmpctx);
tlv->fail_preapprove = tal_dup(tlv, bool,
&ld->dev_hsmd_fail_preapprove);
tlv->no_preapprove_check = tal_dup(tlv, bool,
&ld->dev_hsmd_no_preapprove_check);
tlv->warn_on_overgrind = tal_dup(tlv, bool,
&ld->dev_hsmd_warn_on_overgrind);
msg = towire_hsmd_dev_preinit(tmpctx, tlv);
if (!wire_sync_write(ld->hsm_fd, msg))
err(EXITCODE_HSM_GENERIC_ERROR, "Writing preinit msg to hsm");
}
/* Create TLV for passphrase if needed */
struct tlv_hsmd_init_tlvs *tlv = NULL;
if (ld->hsm_passphrase) {
tlv = tlv_hsmd_init_tlvs_new(tmpctx);
tlv->hsm_passphrase = tal_strdup(tlv, ld->hsm_passphrase);
}
if (!wire_sync_write(ld->hsm_fd, towire_hsmd_init(tmpctx,
&chainparams->bip32_key_version,
chainparams,
NULL,
ld->dev_force_privkey,
ld->dev_force_bip32_seed,
ld->dev_force_channel_secrets,
ld->dev_force_channel_secrets_shaseed,
HSM_MIN_VERSION,
HSM_MAX_VERSION,
tlv)))
err(EXITCODE_HSM_GENERIC_ERROR, "Writing init msg to hsm");
bip32_base = tal(ld, struct ext_key);
msg = wire_sync_read(tmpctx, ld->hsm_fd);
/* Check for init reply failure first */
u32 error_code;
char *error_message;
if (fromwire_hsmd_init_reply_failure(tmpctx, msg, &error_code, &error_message)) {
/* HSM initialization failed: tell user the error (particularly to give feedback if it's a bad passphrase! */
errx(error_code, "HSM initialization failed: %s", error_message);
}
/* Check for successful init reply */
struct tlv_hsmd_init_reply_v4_tlvs *tlvs;
if (fromwire_hsmd_init_reply_v4(ld, msg,
&hsm_version,
&ld->hsm_capabilities,
&ld->our_nodeid, bip32_base,
&unused, &tlvs)) {
/* nothing to do. */
} else {
/* Unknown message type */
errx(EXITCODE_HSM_GENERIC_ERROR, "HSM sent unknown message type");
}
if (!pubkey_from_node_id(&ld->our_pubkey, &ld->our_nodeid))
errx(EXITCODE_HSM_GENERIC_ERROR, "HSM gave invalid node id %s",
fmt_node_id(tmpctx, &ld->our_nodeid));
if (hsm_version < HSM_MIN_VERSION)
errx(EXITCODE_HSM_GENERIC_ERROR,
"HSM version %u below minimum %u",
hsm_version, HSM_MIN_VERSION);
if (hsm_version > HSM_MAX_VERSION)
errx(EXITCODE_HSM_GENERIC_ERROR,
"HSM version %u above maximum %u",
hsm_version, HSM_MAX_VERSION);
/* Debugging help */
for (size_t i = 0; i < tal_count(ld->hsm_capabilities); i++) {
log_debug(ld->hsm->log, "capability +%s",
hsmd_wire_name(ld->hsm_capabilities[i]));
}
if (feature_offered(ld->our_features->bits[INIT_FEATURE],
OPT_ANCHORS_ZERO_FEE_HTLC_TX)
&& !hsm_capable(ld, WIRE_HSMD_SIGN_ANCHORSPEND)) {
fatal("anchors needs HSM capable of signing anchors!");
}
if (feature_offered(ld->our_features->bits[INIT_FEATURE],
OPT_SPLICE)
&& !hsm_capable(ld, WIRE_HSMD_SIGN_SPLICE_TX)) {
fatal("--experimental-splicing needs HSM capable of signing splices!");
}
/* Check if we have a mnemonic-based HSM secret from TLV */
if (tlvs->bip86_base) {
ld->bip86_base = tal_steal(ld, tlvs->bip86_base);
log_info(ld->log, "Using BIP86 for new addresses, BIP32 for channels (mnemonic HSM secret)");
} else {
/* Legacy HSM secret - don't attempt BIP86 derivation */
log_info(ld->log, "Using BIP32 derivation for all operations (legacy HSM secret)");
ld->bip86_base = NULL;
}
/* Free the TLV structure to prevent memory leak */
if (tlvs)
tal_free(tlvs);
/* This is equivalent to makesecret("bolt12-invoice-base") */
msg = towire_hsmd_derive_secret(NULL, tal_dup_arr(tmpctx, u8,
(const u8 *)BOLT12_ID_BASE_STRING,
strlen(BOLT12_ID_BASE_STRING), 0));
if (!wire_sync_write(ld->hsm_fd, take(msg)))
err(EXITCODE_HSM_GENERIC_ERROR, "Writing derive_secret msg to hsm");
msg = wire_sync_read(tmpctx, ld->hsm_fd);
if (!fromwire_hsmd_derive_secret_reply(msg, &ld->invoicesecret_base))
err(EXITCODE_HSM_GENERIC_ERROR, "Bad derive_secret_reply");
/* This is equivalent to makesecret("node-alias-base") */
msg = towire_hsmd_derive_secret(NULL, tal_dup_arr(tmpctx, u8,
(const u8 *)NODE_ALIAS_BASE_STRING,
strlen(NODE_ALIAS_BASE_STRING), 0));
if (!wire_sync_write(ld->hsm_fd, take(msg)))
err(EXITCODE_HSM_GENERIC_ERROR, "Writing derive_secret msg to hsm");
msg = wire_sync_read(tmpctx, ld->hsm_fd);
if (!fromwire_hsmd_derive_secret_reply(msg, &ld->nodealias_base))
err(EXITCODE_HSM_GENERIC_ERROR, "Bad derive_secret_reply");
return bip32_base;
}
/*~ There was a nasty LND bug report where the user issued an address which it
* couldn't spend, presumably due to a bitflip. We check every address using our
* hsm, to be sure it's valid. Expensive, but not as expensive as losing BTC! */
/* Verify a derived public key with the HSM */
/*~ There was a nasty LND bug report where the user issued an address which it
* couldn't spend, presumably due to a bitflip. We check every address using our
* hsm, to be sure it's valid. Expensive, but not as expensive as losing BTC! */
void bip32_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index)
{
const uint32_t flags = BIP32_FLAG_KEY_PUBLIC | BIP32_FLAG_SKIP_HASH;
struct ext_key ext;
if (index >= BIP32_INITIAL_HARDENED_CHILD)
fatal("Can't derive key %u (too large!)", index);
if (bip32_key_from_parent(ld->bip32_base, index, flags, &ext) != WALLY_OK)
fatal("Can't derive key %u", index);
if (!secp256k1_ec_pubkey_parse(secp256k1_ctx, &pubkey->pubkey,
ext.pub_key, sizeof(ext.pub_key)))
fatal("Can't parse derived key %u", index);
/* Don't assume hsmd supports it! */
if (hsm_capable(ld, WIRE_HSMD_CHECK_PUBKEY)) {
bool ok;
const u8 *msg = towire_hsmd_check_pubkey(NULL, index, pubkey);
msg = hsm_sync_req(tmpctx, ld, take(msg));
if (!fromwire_hsmd_check_pubkey_reply(msg, &ok))
fatal("Invalid check_pubkey_reply from hsm");
if (!ok)
fatal("HSM said key derivation of %u != %s",
index, fmt_pubkey(tmpctx, pubkey));
}
}
/* Derive BIP86 public key from the base key */
void bip86_pubkey(struct lightningd *ld, struct pubkey *pubkey, u32 index)
{
const uint32_t flags = BIP32_FLAG_KEY_PUBLIC | BIP32_FLAG_SKIP_HASH;
struct ext_key ext;
u32 path[2];
if (index >= BIP32_INITIAL_HARDENED_CHILD)
fatal("Can't derive key %u (too large!)", index);
/* BIP86 path: m/86'/0'/0'/0/index */
path[0] = 0; /* change (0 for receive) */
path[1] = index; /* address_index */
assert(ld->bip86_base != NULL);
if (bip32_key_from_parent_path(ld->bip86_base, path, 2, flags, &ext) != WALLY_OK)
fatal("Can't derive key %u", index);
if (!secp256k1_ec_pubkey_parse(secp256k1_ctx, &pubkey->pubkey,
ext.pub_key, sizeof(ext.pub_key)))
fatal("Can't parse derived key %u", index);
/* Don't assume hsmd supports it! */
if (hsm_capable(ld, WIRE_HSMD_CHECK_BIP86_PUBKEY)) {
bool ok;
const u8 *msg = towire_hsmd_check_bip86_pubkey(NULL, index, pubkey);
msg = hsm_sync_req(tmpctx, ld, take(msg));
if (!fromwire_hsmd_check_bip86_pubkey_reply(msg, &ok))
fatal("Invalid check_bip86_pubkey_reply from hsm");
if (!ok)
fatal("HSM said BIP86 key derivation of %u != %s",
index, fmt_pubkey(tmpctx, pubkey));
}
}
const u8 *hsm_sync_req(const tal_t *ctx, struct lightningd *ld, const u8 *msg)
{
int type = fromwire_peektype(msg);
if (!wire_sync_write(ld->hsm_fd, msg))
fatal("Writing %s hsm", hsmd_wire_name(type));
msg = wire_sync_read(ctx, ld->hsm_fd);
if (!msg)
fatal("EOF reading from HSM after %s",
hsmd_wire_name(type));
return msg;
}
static struct command_result *json_makesecret(struct command *cmd,
const char *buffer,
const jsmntok_t *obj UNNEEDED,
const jsmntok_t *params)
{
u8 *data;
const char *strdata;
struct json_stream *response;
struct secret secret;
if (!param_check(cmd, buffer, params,
p_opt("hex", param_bin_from_hex, &data),
p_opt("string", param_string, &strdata),
NULL))
return command_param_failed();
if (strdata) {
if (data)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Cannot have both hex and string");
data = tal_dup_arr(cmd, u8, (u8 *)strdata, strlen(strdata), 0);
} else {
if (!data)
return command_fail(cmd, JSONRPC2_INVALID_PARAMS,
"Must have either hex or string");
}
if (command_check_only(cmd))
return command_check_done(cmd);
u8 *msg = towire_hsmd_derive_secret(cmd, data);
if (!wire_sync_write(cmd->ld->hsm_fd, take(msg)))
return command_fail(cmd, LIGHTNINGD,
"Could not write to HSM: %s", strerror(errno));
msg = wire_sync_read(tmpctx, cmd->ld->hsm_fd);
if (!fromwire_hsmd_derive_secret_reply(msg, &secret))
return command_fail(cmd, LIGHTNINGD,
"Bad reply from HSM: %s", strerror(errno));
response = json_stream_success(cmd);
json_add_secret(response, "secret", &secret);
return command_success(cmd, response);
}
static const struct json_command makesecret_command = {
"makesecret",
&json_makesecret,
};
AUTODATA(json_command, &makesecret_command);