From 7f18ed7743eda3b38d8df8e69ff5896e185aae75 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 25 Oct 2023 07:40:04 +1030 Subject: [PATCH] lightningd: allow --recover to take a 64-char hex string. Signed-off-by: Rusty Russell Changelog-Added: Config: `--recover` can take a 32-byte hex string, as well as codex32. --- doc/lightningd-config.5.md | 4 +-- lightningd/options.c | 56 ++++++++++++++++++++++++++------------ lightningd/options.h | 12 ++++++++ tests/test_misc.py | 8 +++++- 4 files changed, 59 insertions(+), 21 deletions(-) diff --git a/doc/lightningd-config.5.md b/doc/lightningd-config.5.md index 0a91c2bb24a5..5301962be6f3 100644 --- a/doc/lightningd-config.5.md +++ b/doc/lightningd-config.5.md @@ -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* diff --git a/lightningd/options.c b/lightningd/options.c index 119afbb9b85b..63e0c5929270 100644 --- a/lightningd/options.c +++ b/lightningd/options.c @@ -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); @@ -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)); @@ -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."); @@ -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 diff --git a/lightningd/options.h b/lightningd/options.h index 0c99a3a7bbe8..abd16fac8596 100644 --- a/lightningd/options.h +++ b/lightningd/options.h @@ -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, diff --git a/tests/test_misc.py b/tests/test_misc.py index 94c0b5f2c6a8..2d3212d52b95 100644 --- a/tests/test_misc.py +++ b/tests/test_misc.py @@ -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()