diff --git a/doc/lightning-hsmtool.8.md b/doc/lightning-hsmtool.8.md index 01c3e9f0d..4a9317a44 100644 --- a/doc/lightning-hsmtool.8.md +++ b/doc/lightning-hsmtool.8.md @@ -68,6 +68,11 @@ We need the path to the hsm\_secret containing the wallet seed, and an optional To generate descriptors using testnet master keys, you may specify *testnet* as the last parameter. By default, mainnet-encoded keys are generated. +**makerune** *hsm\_secret* + Make a master rune for this node (with `uniqueid` 0) +This produces the same results as lightning-commando-rune(7) on a fresh node. +You will still need to create a rune once the node starts, if you want commando to work (as it is only activated once it has generated one). + BUGS ---- diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 61d5f2075..de0e0ace9 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1667,3 +1667,34 @@ def test_upgradewallet(node_factory, bitcoind): sync_blockheight(l1.bitcoin, [l1]) upgrade = l1.rpc.upgradewallet(feerate="urgent", reservedok=True) assert upgrade['upgraded_outs'] == 0 + + +def test_hsmtool_makerune(node_factory): + """Test we can make a valid rune before the node really exists""" + l1 = node_factory.get_node(start=False) + + # get_node() creates a secret, but in usual case we generate one. + hsm_path = os.path.join(l1.daemon.lightning_dir, TEST_NETWORK, "hsm_secret") + os.remove(hsm_path) + + hsmtool = HsmTool(node_factory.directory, "generatehsm", hsm_path) + master_fd, slave_fd = os.openpty() + hsmtool.start(stdin=slave_fd) + hsmtool.wait_for_log(r"Select your language:") + write_all(master_fd, "0\n".encode("utf-8")) + hsmtool.wait_for_log(r"Introduce your BIP39 word list") + write_all(master_fd, "ritual idle hat sunny universe pluck key alpha wing " + "cake have wedding\n".encode("utf-8")) + hsmtool.wait_for_log(r"Enter your passphrase:") + write_all(master_fd, "This is actually not a passphrase\n".encode("utf-8")) + assert hsmtool.proc.wait(WAIT_TIMEOUT) == 0 + hsmtool.is_in_log(r"New hsm_secret file created") + + cmd_line = ["tools/hsmtool", "makerune", hsm_path] + out = subprocess.check_output(cmd_line).decode("utf8").split("\n")[0] + + l1.start() + + # We have to generate a rune now, for commando to even start processing! + rune = l1.rpc.commando_rune()['rune'] + assert rune == out diff --git a/tools/hsmtool.c b/tools/hsmtool.c index 784dd87b5..447fdd253 100644 --- a/tools/hsmtool.c +++ b/tools/hsmtool.c @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -42,6 +43,7 @@ static void show_usage(const char *progname) printf(" - generatehsm \n"); printf(" - checkhsm \n"); printf(" - dumponchaindescriptors [network]\n"); + printf(" - makerune \n"); exit(0); } @@ -611,6 +613,33 @@ static int check_hsm(const char *hsm_secret_path) return 0; } +static int make_rune(const char *hsm_secret_path) +{ + struct secret hsm_secret, derived_secret, rune_secret; + struct rune *master_rune, *rune; + + /* Get hsm_secret */ + get_hsm_secret(&hsm_secret, hsm_secret_path); + + /* HSM derives a root secret for `makesecret` */ + hkdf_sha256(&derived_secret, sizeof(struct secret), NULL, 0, + &hsm_secret, sizeof(hsm_secret), + "derived secrets", strlen("derived secrets")); + + /* Commando derives secret using makesecret "commando" */ + hkdf_sha256(&rune_secret, sizeof(struct secret), NULL, 0, + &derived_secret, sizeof(derived_secret), + "commando", strlen("commando")); + + master_rune = rune_new(tmpctx, + rune_secret.data, + ARRAY_SIZE(rune_secret.data), + NULL); + rune = rune_derive_start(tmpctx, master_rune, "0"); + printf("%s\n", rune_to_base64(tmpctx, rune)); + return 0; +} + int main(int argc, char *argv[]) { const char *method; @@ -703,5 +732,11 @@ int main(int argc, char *argv[]) return check_hsm(argv[2]); } + if (streq(method, "makerune")) { + if (argc < 3) + show_usage(argv[0]); + return make_rune(argv[2]); + } + show_usage(argv[0]); }