diff --git a/README.md b/README.md index 2ee9f1ebd811..8beb3cb00e2d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ c-lightning is a lighweight, highly customizable and [standard compliant][std] i * [Configuration File](#configuration-file) * [Further Information](#further-information) * [Pruning](#pruning) + * [HD wallet encryption](#hd-wallet-encryption) * [Developers](#developers) ## Project Status @@ -102,8 +103,6 @@ Once you've started for the first time, there's a script called `contrib/bootstrap-node.sh` which will connect you to other nodes on the lightning network. -You can encrypt the BIP32 root seed (what is stored in `hsm_secret`) by passing the `--encrypted-hsm` startup argument. You can start `lightningd` with `--encrypted-hsm` on an already existing `lightning-dir` (with a not encrypted `hsm_secret`). If you pass that option, you __will not__ be able to start `lightningd` (with the same wallet) again without the password, so please beware with your password management. Also beware of not feeling too safe with an encrypted `hsm_secret`: unlike for `bitcoind` where the wallet encryption can restrict the usage of some RPC command, `lightningd` always need to access keys from the wallet which is thus __not locked__ (yet), even with an encrypted BIP32 master seed. - There are also numerous plugins available for c-lightning which add capabilities: in particular there's a collection at: @@ -112,6 +111,9 @@ capabilities: in particular there's a collection at: Including [helpme][helpme-github] which guides you through setting up your first channels and customizing your node. +For a less reckless experience, you can encrypt the HD wallet seed: + see [HD wallet encryption](#hd-wallet-encryption). + You can also chat to other users at [#c-lightning @ freenode.net][irc2]; we are always happy to help you get started! @@ -202,6 +204,12 @@ If `bitcoind` prunes a block that c-lightning has not processed yet, e.g., c-lig In order to avoid this situation you should be monitoring the gap between c-lightning's blockheight using `lightning-cli getinfo` and `bitcoind`'s blockheight using `bitcoin-cli getblockchaininfo`. If the two blockheights drift apart it might be necessary to intervene. +### HD wallet encryption + +You can encrypt the `hsm_secret` content (which is used to derive the HD wallet's master key) by passing the `--encrypted-hsm` startup argument, or by using the `hsmtools` tool with the `encrypthsm` method. You can unencrypt an encrypted `hsm_secret` using the `hsmtools` tool with the `decrypthsm` method. + +If you encrypt your `hsm_secret`, you will have to pass the `--encrypted-hsm` startup option to `lightningd`. Once your `hsm_secret` is encrypted, you __will not__ be able to access your funds without your password, so please beware with your password management. Also beware of not feeling too safe with an encrypted `hsm_secret`: unlike for `bitcoind` where the wallet encryption can restrict the usage of some RPC command, `lightningd` always need to access keys from the wallet which is thus __not locked__ (yet), even with an encrypted BIP32 master seed. + ### Developers Developers wishing to contribute should start with the developer guide [here](doc/HACKING.md). diff --git a/tests/test_wallet.py b/tests/test_wallet.py index e635610dc97d..8de11d097c67 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -567,7 +567,7 @@ def test_transaction_annotations(node_factory, bitcoind): @unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.") -def test_hsm_secret_encryption(node_factory, executor): +def test_hsm_secret_encryption(node_factory): l1 = node_factory.get_node() password = "reckful\n" # We need to simulate a terminal to use termios in `lightningd`. @@ -588,7 +588,6 @@ def test_hsm_secret_encryption(node_factory, executor): l1.daemon.start(stdin=slave_fd, stderr=subprocess.STDOUT, wait_for_initialized=False) time.sleep(3 if SLOW_MACHINE else 1) - os.write(master_fd, password[2:].encode("utf-8")) err = "hsm_secret is encrypted, you need to pass the --encrypted-hsm startup option." assert l1.daemon.is_in_log(err) @@ -606,3 +605,61 @@ def test_hsm_secret_encryption(node_factory, executor): os.write(master_fd, password.encode("utf-8")) l1.daemon.wait_for_log("Server started with public key") assert id == l1.rpc.getinfo()["id"] + + +@unittest.skipIf(VALGRIND, "It does not play well with prompt and key derivation.") +def test_hsmtools_secret_decryption(node_factory): + l1 = node_factory.get_node() + password = "reckless\n" + hsm_path = os.path.join(l1.daemon.lightning_dir, "hsm_secret") + # We need to simulate a terminal to use termios in `lightningd`. + master_fd, slave_fd = os.openpty() + + # Encrypt the master seed + l1.rpc.stop() + l1.daemon.opts.update({"encrypted-hsm": None}) + l1.daemon.start(stdin=slave_fd, wait_for_initialized=False) + time.sleep(3 if SLOW_MACHINE else 1) + os.write(master_fd, password.encode("utf-8")) + l1.daemon.wait_for_log("Server started with public key") + node_id = l1.rpc.getinfo()["id"] + l1.stop() + + # We can't use a wrong password ! + cmd_line = ["tools/hsmtools", "decrypthsm", hsm_path, "A wrong pass"] + assert subprocess.Popen(cmd_line).wait() != 0 + + # Decrypt it with hsmtools + cmd_line[3] = password[:-1] + assert subprocess.Popen(cmd_line).wait() == 0 + # Then test we can now start it without password + l1.daemon.opts.pop("encrypted-hsm") + l1.daemon.start(stdin=slave_fd, wait_for_initialized=True) + assert node_id == l1.rpc.getinfo()["id"] + l1.stop() + + # Test we can encrypt it offline + cmd_line[1] = "encrypthsm" + assert subprocess.Popen(cmd_line).wait() == 0 + # Now we need to pass the encrypted-hsm startup option + l1.stop() + l1.daemon.start(stdin=slave_fd, stderr=subprocess.STDOUT, + wait_for_initialized=False) + time.sleep(3 if SLOW_MACHINE else 1) + err = "hsm_secret is encrypted, you need to pass the --encrypted-hsm startup option." + assert l1.daemon.is_in_log(err) + l1.daemon.opts.update({"encrypted-hsm": None}) + l1.daemon.start(stdin=slave_fd, stderr=subprocess.STDOUT, + wait_for_initialized=False) + time.sleep(3 if SLOW_MACHINE else 1) + os.write(master_fd, password.encode("utf-8")) + l1.daemon.wait_for_log("Server started with public key") + assert node_id == l1.rpc.getinfo()["id"] + l1.stop() + + # And finally test that we can also decrypt if encrypted with hsmtools + cmd_line[1] = "decrypthsm" + assert subprocess.Popen(cmd_line).wait() == 0 + l1.daemon.opts.pop("encrypted-hsm") + l1.daemon.start(stdin=slave_fd, wait_for_initialized=True) + assert node_id == l1.rpc.getinfo()["id"] diff --git a/tools/Makefile b/tools/Makefile index e267e7d45b37..ce9319d48845 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -1,17 +1,26 @@ #! /usr/bin/make +TOOLS := tools/hsmtools tools/check-bolt +TOOLS_SRC := $(TOOLS:=.c) +TOOLS_OBJ := $(TOOLS_SRC:.c=.o) +ALL_PROGRAMS += $(TOOLS) +ALL_OBJS += $(TOOLS_OBJ) + +TOOLS_COMMON_OBJS = common/utils.o # We force make to relink this every time, to detect version changes. # Do it atomically, otherwise parallel builds can get upset! tools/headerversions: FORCE tools/headerversions.o $(CCAN_OBJS) @trap "rm -f $@.tmp.$$$$" EXIT; $(LINK.o) tools/headerversions.o $(CCAN_OBJS) $(LOADLIBES) $(LDLIBS) -o $@.tmp.$$$$ && mv $@.tmp.$$$$ $@ -tools/check-bolt: tools/check-bolt.o $(CCAN_OBJS) common/utils.o -tools/check-bolt.o: $(CCAN_HEADERS) +tools/check-bolt: tools/check-bolt.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) + +tools/hsmtools: tools/hsmtools.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) $(BITCOIN_OBJS) common/amount.o common/bech32.o common/bigsize.o common/derive_basepoints.o common/key_derive.o common/node_id.o common/type_to_string.o wire/fromwire.o wire/towire.o clean: tools-clean tools-clean: $(RM) tools/check-bolt tools/*.o $(RM) tools/headerversions + $(RM) $(TOOLS_OBJ) $(TOOLS) include tools/test/Makefile diff --git a/tools/hsmtools.c b/tools/hsmtools.c new file mode 100644 index 000000000000..ab04bc34c24e --- /dev/null +++ b/tools/hsmtools.c @@ -0,0 +1,448 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ERROR_HSM_FILE errno +#define ERROR_USAGE 2 +#define ERROR_LIBSODIUM 3 +#define ERROR_LIBWALLY 4 +#define ERROR_KEYDERIV 5 + +static void show_usage(void) +{ + printf("./hsmtools [arguments]\n"); + printf("methods:\n"); + printf(" - decrypthsm \n"); + printf(" - encrypthsm \n"); + printf(" - dumpcommitments " + " [hsm_secret password]\n"); + printf(" - guesscommitment " + " [hsm_secret password]\n"); + exit(0); +} + +static bool ensure_hsm_secret_exists(int fd) +{ + if (fsync(fd) != 0) { + close(fd); + return false; + } + if (close(fd) != 0) + return false; + + fd = open(".", O_RDONLY); + if (fd < 0) + return false; + if (fsync(fd) != 0) { + close(fd); + return false; + } + + close(fd); + return true; +} + +static void get_hsm_secret(struct secret *hsm_secret, + const char *hsm_secret_path) +{ + int fd; + + fd = open(hsm_secret_path, O_RDONLY); + if (fd < 0) + errx(ERROR_HSM_FILE, "Could not open hsm_secret"); + if (!read_all(fd, hsm_secret, sizeof(*hsm_secret))) + errx(ERROR_HSM_FILE, "Could not read hsm_secret"); + close(fd); +} + +/* Derive the encryption key from the password provided, and try to decrypt + * the cipher. */ +static void get_encrypted_hsm_secret(struct secret *hsm_secret, + const char *hsm_secret_path, + const char *passwd) +{ + int fd; + struct secret key; + u8 salt[16] = "c-lightning\0\0\0\0\0"; + crypto_secretstream_xchacha20poly1305_state crypto_state; + u8 header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; + /* The cipher size is static with xchacha20poly1305. */ + u8 cipher[sizeof(struct secret) + crypto_secretstream_xchacha20poly1305_ABYTES]; + + fd = open(hsm_secret_path, O_RDONLY); + if (fd < 0) + errx(ERROR_HSM_FILE, "Could not open hsm_secret"); + + if (!read_all(fd, header, crypto_secretstream_xchacha20poly1305_HEADERBYTES)) + errx(ERROR_HSM_FILE, "Could not read cipher header"); + if (!read_all(fd, cipher, sizeof(cipher))) + errx(ERROR_HSM_FILE, "Could not read cipher body"); + + if (crypto_pwhash(key.data, sizeof(key.data), passwd, strlen(passwd), salt, + crypto_pwhash_argon2id_OPSLIMIT_MODERATE, + crypto_pwhash_argon2id_MEMLIMIT_MODERATE, + crypto_pwhash_ALG_ARGON2ID13) != 0) + errx(ERROR_LIBSODIUM, "Could not derive a key from the password."); + if (crypto_secretstream_xchacha20poly1305_init_pull(&crypto_state, header, + key.data) != 0) + errx(ERROR_LIBSODIUM, "Could not initialize the crypto state"); + if (crypto_secretstream_xchacha20poly1305_pull(&crypto_state, hsm_secret->data, + NULL, 0, cipher, sizeof(cipher), + NULL, 0) != 0) + errx(ERROR_LIBSODIUM, "Could not retrieve the seed. Wrong password ?"); + + close(fd); +} + +/* Taken from hsmd. */ +static void get_channel_seed(struct secret *channel_seed, struct node_id *peer_id, + u64 dbid, struct secret *hsm_secret) +{ + struct secret channel_base; + u8 input[sizeof(peer_id->k) + sizeof(dbid)]; + /*~ Again, "per-peer" should be "per-channel", but Hysterical Raisins */ + const char *info = "per-peer seed"; + + /*~ We use the DER encoding of the pubkey, because it's platform + * independent. Since the dbid is unique, however, it's completely + * unnecessary, but again, existing users can't be broken. */ + /* FIXME: lnd has a nicer BIP32 method for deriving secrets which we + * should migrate to. */ + hkdf_sha256(&channel_base, sizeof(struct secret), NULL, 0, + hsm_secret, sizeof(*hsm_secret), + /*~ Initially, we didn't support multiple channels per + * peer at all: a channel had to be completely forgotten + * before another could exist. That was slightly relaxed, + * but the phrase "peer seed" is wired into the seed + * generation here, so we need to keep it that way for + * existing clients, rather than using "channel seed". */ + "peer seed", strlen("peer seed")); + memcpy(input, peer_id->k, sizeof(peer_id->k)); + BUILD_ASSERT(sizeof(peer_id->k) == PUBKEY_CMPR_LEN); + /*~ For all that talk about platform-independence, note that this + * field is endian-dependent! But let's face it, little-endian won. + * In related news, we don't support EBCDIC or middle-endian. */ + memcpy(input + PUBKEY_CMPR_LEN, &dbid, sizeof(dbid)); + + hkdf_sha256(channel_seed, sizeof(*channel_seed), + input, sizeof(input), + &channel_base, sizeof(channel_base), + info, strlen(info)); +} + +static int decrypt_hsm(const char *hsm_secret_path, const char *passwd) +{ + int fd; + struct stat st; + struct secret hsm_secret; + + if (sodium_init() == -1) + errx(ERROR_LIBSODIUM, + "Could not initialize libsodium. Not enough entropy ?"); + + if (stat(hsm_secret_path, &st) != 0) + errx(ERROR_HSM_FILE, "Could not stat hsm_secret"); + if (st.st_size <= 32) + errx(ERROR_HSM_FILE, "hsm_secret is not encrypted"); + get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd); + + /* Create a backup file, "just in case". */ + rename(hsm_secret_path, "hsm_secret.backup"); + fd = open(hsm_secret_path, O_CREAT|O_EXCL|O_WRONLY, 0400); + if (fd < 0) + errx(ERROR_HSM_FILE, "Could not open new hsm_secret"); + + if (!write_all(fd, &hsm_secret, sizeof(hsm_secret))) { + unlink_noerr(hsm_secret_path); + close(fd); + rename("hsm_secret.backup", hsm_secret_path); + errx(ERROR_HSM_FILE, + "Failure writing plaintext seed to hsm_secret."); + } + + /* Be as paranoïd as in hsmd with the file state on disk. */ + if (!ensure_hsm_secret_exists(fd)) { + unlink_noerr(hsm_secret_path); + rename("hsm_secret.backup", hsm_secret_path); + errx(ERROR_HSM_FILE, + "Could not ensure hsm_secret existence."); + } + unlink_noerr("hsm_secret.backup"); + + printf("Succesfully decrypted hsm_secret, be careful now :-).\n"); + return 0; +} + +static int encrypt_hsm(const char *hsm_secret_path, const char *passwd) +{ + int fd; + struct stat st; + struct secret key, hsm_secret; + u8 salt[16] = "c-lightning\0\0\0\0\0"; + crypto_secretstream_xchacha20poly1305_state crypto_state; + u8 header[crypto_secretstream_xchacha20poly1305_HEADERBYTES]; + /* The cipher size is static with xchacha20poly1305. */ + u8 cipher[sizeof(struct secret) + crypto_secretstream_xchacha20poly1305_ABYTES]; + + if (sodium_init() == -1) + errx(ERROR_LIBSODIUM, + "Could not initialize libsodium. Not enough entropy ?"); + + if (stat(hsm_secret_path, &st) != 0) + errx(ERROR_HSM_FILE, "Could not stat hsm_secret"); + if (st.st_size > 32) + errx(ERROR_USAGE, "hsm_secret is already encrypted"); + get_hsm_secret(&hsm_secret, hsm_secret_path); + + /* Derive the encryption key from the password provided, and try to encrypt + * the seed. */ + if (crypto_pwhash(key.data, sizeof(key.data), passwd, strlen(passwd), salt, + crypto_pwhash_argon2id_OPSLIMIT_MODERATE, + crypto_pwhash_argon2id_MEMLIMIT_MODERATE, + crypto_pwhash_ALG_ARGON2ID13) != 0) + errx(ERROR_LIBSODIUM, "Could not derive a key from the password."); + if (crypto_secretstream_xchacha20poly1305_init_push(&crypto_state, header, + key.data) != 0) + errx(ERROR_LIBSODIUM, "Could not initialize the crypto state"); + if (crypto_secretstream_xchacha20poly1305_push(&crypto_state, cipher, + NULL, hsm_secret.data, + sizeof(hsm_secret.data), + NULL, 0, 0) != 0) + errx(ERROR_LIBSODIUM, "Could not encrypt the seed."); + + /* Create a backup file, "just in case". */ + rename(hsm_secret_path, "hsm_secret.backup"); + fd = open(hsm_secret_path, O_CREAT|O_EXCL|O_WRONLY, 0400); + if (fd < 0) + errx(ERROR_HSM_FILE, "Could not open new hsm_secret"); + + /* Write the encrypted hsm_secret. */ + if (!write_all(fd, header, sizeof(header)) + || !write_all(fd, cipher, sizeof(cipher))) { + unlink_noerr(hsm_secret_path); + close(fd); + rename("hsm_secret.backup", hsm_secret_path); + errx(ERROR_HSM_FILE, "Failure writing cipher to hsm_secret."); + } + + /* Be as paranoïd as in hsmd with the file state on disk. */ + if (!ensure_hsm_secret_exists(fd)) { + unlink_noerr(hsm_secret_path); + rename("hsm_secret.backup", hsm_secret_path); + errx(ERROR_HSM_FILE, "Could not ensure hsm_secret existence."); + } + unlink_noerr("hsm_secret.backup"); + + printf("Succesfully encrypted hsm_secret. You'll now have to pass the " + "--encrypted-hsm startup option.\n"); + return 0; +} + +static int dump_commitments_infos(struct node_id *node_id, u64 channel_id, + u64 depth, char *hsm_secret_path, char *passwd) +{ + struct sha256 shaseed; + struct secret hsm_secret, channel_seed, per_commitment_secret; + struct pubkey per_commitment_point; + + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY + | SECP256K1_CONTEXT_SIGN); + + if (passwd) + get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd); + else + get_hsm_secret(&hsm_secret, hsm_secret_path); + get_channel_seed(&channel_seed, node_id, channel_id, &hsm_secret); + + derive_shaseed(&channel_seed, &shaseed); + printf("shaseed: %s\n", type_to_string(tmpctx, struct sha256, &shaseed)); + for (u64 i = 0; i < depth; i++) { + if (!per_commit_secret(&shaseed, &per_commitment_secret, i)) + errx(ERROR_KEYDERIV, "Could not derive secret #%"PRIu64, i); + printf("commit secret #%"PRIu64": %s\n", + i, tal_hexstr(tmpctx, per_commitment_secret.data, + sizeof(per_commitment_secret.data))); + if (!per_commit_point(&shaseed, &per_commitment_point, i)) + errx(ERROR_KEYDERIV, "Could not derive point #%"PRIu64, i); + printf("commit point #%"PRIu64": %s\n", + i, type_to_string(tmpctx, struct pubkey, &per_commitment_point)); + } + + return 0; +} + +/* In case of an unilateral close from the remote side while we suffered a + * loss of data, this tries to recover the private key from the `to_remote` + * output. + * This basically iterates over every `dbid` to derive the channel_seed and + * then derives as many payment keys as indicated to compare to the pubkey + * hash specified in the witness programm. + * + * :param address: The bech32 address of the P2WPKH output + * :param node_id: The id of the node with which the channel was established + * :param depth: How many commitments to derive for each dbid (the basepoint + * is checked by default). + * :param hsm_secret_path: The path to the hsm_secret + * :param passwd: The *optional* hsm_secret password + */ +static int guess_commitment(const char *address, struct node_id *node_id, + u64 depth, char *hsm_secret_path, char *passwd) +{ + struct sha256 shaseed; + struct secret hsm_secret, channel_seed, basepoint_secret; + struct privkey payment_privkey; + struct pubkey per_commitment_point, basepoint, payment_pubkey; + struct ripemd160 pubkeyhash; + /* We only support P2WPKH, hence 20 */ + u8 goal_pubkeyhash[20]; + /* See common/bech32.h for buffer size */ + char hrp[strlen(address) - 6]; + int witver; + size_t witlen; + + /* Get the hrp to accept addresses from any network */ + if (bech32_decode(hrp, goal_pubkeyhash, &witlen, address, 90) != 1) + errx(ERROR_USAGE, "Could not get address' network"); + if (segwit_addr_decode(&witver, goal_pubkeyhash, &witlen, hrp, address) != 1) + errx(ERROR_USAGE, "Wrong bech32 address"); + + secp256k1_ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY + | SECP256K1_CONTEXT_SIGN); + + if (passwd) + get_encrypted_hsm_secret(&hsm_secret, hsm_secret_path, passwd); + else + get_hsm_secret(&hsm_secret, hsm_secret_path); + + for (u64 dbid = 0; ; dbid++) { + get_channel_seed(&channel_seed, node_id, dbid, &hsm_secret); + if (!derive_payment_basepoint(&channel_seed, + &basepoint, &basepoint_secret)) + errx(ERROR_KEYDERIV, "Could not derive basepoints for dbid %"PRIu64 + " and channel seed %s.", dbid, + type_to_string(tmpctx, + struct secret, &channel_seed)); + + /* If we specified option_static_remotekey, this is just the basepoint ! */ + pubkey_to_hash160(&basepoint, &pubkeyhash); + if (memcmp(pubkeyhash.u.u8, goal_pubkeyhash, 20) == 0) { + printf("FOUND !\n"); + printf("pubkey hash : %s\n", + tal_hexstr(tmpctx, pubkeyhash.u.u8, 20)); + printf("pubkey : %s \n", + type_to_string(tmpctx, struct pubkey, &basepoint)); + printf("privkey : %s \n", + type_to_string(tmpctx, struct secret, &basepoint_secret)); + return 0; + } + + /* Otherwise derive payment keys up to to check */ + if (!derive_shaseed(&channel_seed, &shaseed)) + errx(ERROR_KEYDERIV, "Could not derive shaseed for dbid %"PRIu64 + " and channel seed %s.", dbid, + type_to_string(tmpctx, + struct secret, &channel_seed)); + for (u64 i = 0; i < depth; i++) { + if (!per_commit_point(&shaseed, &per_commitment_point, i)) + errx(ERROR_KEYDERIV, "Could not derive point #%"PRIu64" for" + " dbid %"PRIu64" and shaseed %s.", i, dbid, + type_to_string(tmpctx, + struct sha256, &shaseed)); + if (!derive_simple_privkey(&basepoint_secret, &basepoint, + &per_commitment_point, &payment_privkey)) + errx(ERROR_KEYDERIV, "Could not derive payment privkey #%"PRIu64 + " for dbid %"PRIu64", basepoint %s, and " + "commit point %s.", i, dbid, + type_to_string(tmpctx, + struct pubkey, &basepoint), + type_to_string(tmpctx, + struct pubkey, &per_commitment_point)); + if (!pubkey_from_privkey(&payment_privkey, &payment_pubkey)) + errx(ERROR_KEYDERIV, "Could not derive payment pubkey #%"PRIu64 + " for dbid %"PRIu64" and privkey %s.", i, + dbid, + type_to_string(tmpctx, + struct privkey, &payment_privkey)); + + pubkey_to_hash160(&payment_pubkey, &pubkeyhash); + if (memcmp(pubkeyhash.u.u8, goal_pubkeyhash, 20) == 0) { + printf("FOUND !\n"); + printf("commit point #%"PRIu64": %s\n", + i, + type_to_string(tmpctx, struct pubkey, &per_commitment_point)); + printf("pubkey hash : %s \n", + tal_hexstr(tmpctx, pubkeyhash.u.u8, 20)); + printf("pubkey : %s \n", + type_to_string(tmpctx, struct pubkey, &payment_pubkey)); + printf("privkey : %s \n", + type_to_string(tmpctx, struct privkey, &payment_privkey)); + return 0; + } + } + } + + return 1; +} + +int main(int argc, char *argv[]) +{ + const char *method; + + setup_locale(); + err_set_progname(argv[0]); + + method = argv[1]; + if (!method) + show_usage(); + + if (streq(method, "decrypthsm")) { + if (!argv[2] || !argv[3]) + show_usage(); + return decrypt_hsm(argv[2], argv[3]); + } + + if (streq(method, "encrypthsm")) { + if (!argv[2] || !argv[3]) + show_usage(); + return encrypt_hsm(argv[2], argv[3]); + } + + if (streq(method, "dumpcommitments")) { + /* node_id channel_id depth hsm_secret ?password? */ + if (!(argv[2] && argv[3] && argv[4] && argv[5])) + show_usage(); + struct node_id node_id; + if (!node_id_from_hexstr(argv[2], strlen(argv[2]), &node_id)) + errx(ERROR_USAGE, "Bad node id"); + return dump_commitments_infos(&node_id, atol(argv[3]), atol(argv[4]), + argv[5], argv[6]); + } + + if (streq(method, "guesscommitment")) { + /* address node_id depth hsm_secret ?password? */ + if (!(argv[2] && argv[3] && argv[4] && argv[5])) + show_usage(); + struct node_id node_id; + if (!node_id_from_hexstr(argv[3], strlen(argv[3]), &node_id)) + errx(ERROR_USAGE, "Bad node id"); + return guess_commitment(argv[2], &node_id, atol(argv[4]), + argv[5], argv[6]); + } + + show_usage(); +}