Skip to content

Commit

Permalink
lightningd: allow --recover to take a 64-char hex string.
Browse files Browse the repository at this point in the history
Signed-off-by: Rusty Russell <[email protected]>
Changelog-Added: Config: `--recover` can take a 32-byte hex string, as well as codex32.
  • Loading branch information
rustyrussell committed Oct 26, 2023
1 parent 3b622f0 commit 7f18ed7
Show file tree
Hide file tree
Showing 4 changed files with 59 additions and 21 deletions.
4 changes: 2 additions & 2 deletions doc/lightningd-config.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -294,9 +294,9 @@ connections; default is not to activate the plugin at all.

### Lightning node customization options

* **recover**=*codex32secret*
* **recover**=*hsmsecret*

Restore the node from a 32-byte secret encoded as a codex32 secret string: this will fail if the `hsm_secret` file exists. Your node will start the node in offline mode, for manual recovery. The secret can be extracted from the `hsm_secret` using hsmtool(8).
Restore the node from a 32-byte secret encoded as either a codex32 secret string or a 64-character hex string: this will fail if the `hsm_secret` file exists. Your node will start the node in offline mode, for manual recovery. The secret can be extracted from the `hsm_secret` using hsmtool(8).

* **alias**=*NAME*

Expand Down
56 changes: 38 additions & 18 deletions lightningd/options.c
Original file line number Diff line number Diff line change
Expand Up @@ -1268,25 +1268,45 @@ static char *opt_set_announce_dns(const char *optarg, struct lightningd *ld)
return opt_set_bool_arg(optarg, &ld->announce_dns);
}

static char *opt_set_codex32(const char *arg, struct lightningd *ld)
char *hsm_secret_arg(const tal_t *ctx,
const char *arg,
const u8 **hsm_secret)
{
char *codex32_fail;
struct codex32 *codex32;

/* We accept hex, or codex32. hex is very very very unlikely to
* give a valid codex32, so try that first */
codex32 = codex32_decode(tmpctx, "cl", arg, &codex32_fail);
if (codex32) {
*hsm_secret = tal_steal(ctx, codex32->payload);
if (codex32->threshold != 0
|| codex32->type != CODEX32_ENCODING_SECRET) {
return "This is only one share of codex32!";
}
} else {
/* Not codex32, was it hex? */
*hsm_secret = tal_hexdata(ctx, arg, strlen(arg));
if (!*hsm_secret) {
/* It's not hex! So give codex32 error */
return codex32_fail;
}
}

if (tal_count(*hsm_secret) != 32)
return "Invalid length: must be 32 bytes";

return NULL;
}

static char *opt_set_codex32_or_hex(const char *arg, struct lightningd *ld)
{
char *err;
struct codex32 *parts = codex32_decode(tmpctx, "cl", arg, &err);
const u8 *payload;

if (!parts) {
err = hsm_secret_arg(tmpctx, arg, &payload);
if (err)
return err;
}

if (parts->type != CODEX32_ENCODING_SECRET) {
return tal_fmt(tmpctx, "Not a valid codex32 secret!");
}

if (tal_bytelen(parts->payload) != 32) {
return tal_fmt(tmpctx, "Expected 32 Byte secret: %s",
tal_hexstr(tmpctx,
parts->payload,
tal_bytelen(parts->payload)));
}

/* Checks if hsm_secret exists */
int fd = open("hsm_secret", O_CREAT|O_EXCL|O_WRONLY, 0400);
Expand All @@ -1299,7 +1319,7 @@ static char *opt_set_codex32(const char *arg, struct lightningd *ld)
strerror(errno));
}

if (!write_all(fd, parts->payload, tal_count(parts->payload))) {
if (!write_all(fd, payload, tal_count(payload))) {
unlink_noerr("hsm_secret");
return tal_fmt(tmpctx, "Writing HSM: %s",
strerror(errno));
Expand Down Expand Up @@ -1384,7 +1404,7 @@ static void register_opts(struct lightningd *ld)
&ld->wallet_dsn,
"Location of the wallet database.");

opt_register_early_arg("--recover", opt_set_codex32, NULL,
opt_register_early_arg("--recover", opt_set_codex32_or_hex, NULL,
ld,
"Populate hsm_secret with the given codex32 secret"
" and starts the node in `offline` mode.");
Expand Down Expand Up @@ -2066,7 +2086,7 @@ bool is_known_opt_cb_arg(char *(*cb_arg)(const char *, void *))
|| cb_arg == (void *)opt_set_db_upgrade
|| cb_arg == (void *)arg_log_to_file
|| cb_arg == (void *)opt_add_accept_htlc_tlv
|| cb_arg == (void *)opt_set_codex32
|| cb_arg == (void *)opt_set_codex32_or_hex
|| cb_arg == (void *)opt_subd_dev_disconnect
|| cb_arg == (void *)opt_force_featureset
|| cb_arg == (void *)opt_force_privkey
Expand Down
12 changes: 12 additions & 0 deletions lightningd/options.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,18 @@ void handle_opts(struct lightningd *ld);
/* Derive default color and alias from the pubkey. */
void setup_color_and_alias(struct lightningd *ld);

/**
* hsm_secret_arg - parse an hsm_secret as hex or codex32
* @ctx: context to allocate @hsm_secret from
* @arg: string to parse
* @hsm_secret: set on success.
*
* Returns NULL on success (and sets hsm_secret) otherwise, error msg
*/
char *hsm_secret_arg(const tal_t *ctx,
const char *arg,
const u8 **hsm_secret);

enum opt_autobool {
OPT_AUTOBOOL_FALSE = 0,
OPT_AUTOBOOL_TRUE = 1,
Expand Down
8 changes: 7 additions & 1 deletion tests/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1399,8 +1399,14 @@ def test_recover(node_factory, bitcoind):
l1.daemon.opts.update({"recover": "CL10LEETSLLHDMN9M42VCSAMX24ZRXGS3QQAT3LTDVAKMT73"})
l1.daemon.start(wait_for_initialized=False, stderr_redir=True)
assert l1.daemon.wait() == 1
assert l1.daemon.is_in_stderr(r"Expected 32 Byte secret: ffeeddccbbaa99887766554433221100")
assert l1.daemon.is_in_stderr(r"Invalid length: must be 32 bytes")

# Can do HSM secret in hex, too!
l1.daemon.opts["recover"] = "6c696768746e696e672d31000000000000000000000000000000000000000000"
l1.daemon.start()
l1.stop()

# And can start without recovery, of course!
l1.daemon.opts.pop("recover")
l1.start()

Expand Down

0 comments on commit 7f18ed7

Please sign in to comment.