diff --git a/README.md b/README.md index d9fcfbd30b74..ad1737ca7f82 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ c-lightning is a lighweight, highly customizable and [standard compliant][std] i * [Further Information](#further-information) * [Pruning](#pruning) * [HD wallet encryption](#hd-wallet-encryption) + * [Data loss](#data-loss) * [Developers](#developers) ## Project Status @@ -210,6 +211,10 @@ You can encrypt the `hsm_secret` content (which is used to derive the HD wallet' 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. +### Data loss + +If you lose data (likely corrupted `lightningd.sqlite3`) about a channel __with `option_static_remotekey` enabled__, you can, after your peer unlateraly closed the channel, use `tools/hsmtool` with the `guesstoremote` command to (try to) recover your output from your peer's published commitment transaction. + ### Developers Developers wishing to contribute should start with the developer guide [here](doc/HACKING.md). diff --git a/tools/Makefile b/tools/Makefile index bd6571f837a8..7f555ed5f59d 100644 --- a/tools/Makefile +++ b/tools/Makefile @@ -14,7 +14,7 @@ tools/headerversions: FORCE tools/headerversions.o $(CCAN_OBJS) tools/check-bolt: tools/check-bolt.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) -tools/hsmtool: tools/hsmtool.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) $(BITCOIN_OBJS) common/amount.o common/bigsize.o common/derive_basepoints.o common/node_id.o common/type_to_string.o wire/fromwire.o wire/towire.o +tools/hsmtool: tools/hsmtool.o $(CCAN_OBJS) $(TOOLS_COMMON_OBJS) $(BITCOIN_OBJS) common/amount.o common/bech32.o common/bigsize.o common/derive_basepoints.o common/node_id.o common/type_to_string.o wire/fromwire.o wire/towire.o clean: tools-clean diff --git a/tools/hsmtool.c b/tools/hsmtool.c index 7c26f2f40465..f0898d7432d6 100644 --- a/tools/hsmtool.c +++ b/tools/hsmtool.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,8 @@ static void show_usage(void) printf(" - encrypt \n"); printf(" - dumpcommitments " " [hsm_secret password]\n"); + printf(" - guesstoremote " + " [hsm_secret password]\n"); exit(0); } @@ -285,6 +288,76 @@ static int dump_commitments_infos(struct node_id *node_id, u64 channel_id, 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 the payment basepoint to compare to the pubkey hash specified + * in the witness programm. + * Note that since a node generates the key for the to_remote output from its + * *local* per_commitment_point, there is nothing we can do if + * `option_static_remotekey` was not negotiated. + * + * :param address: The bech32 address of the v0 P2WPKH witness programm + * :param node_id: The id of the node with which the channel was established + * :param tries: How many dbids to try. + * :param hsm_secret_path: The path to the hsm_secret + * :param passwd: The *optional* hsm_secret password + */ +static int guess_to_remote(const char *address, struct node_id *node_id, + u64 tries, char *hsm_secret_path, char *passwd) +{ + struct secret hsm_secret, channel_seed, basepoint_secret; + struct pubkey basepoint; + 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 < tries ; 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)); + + pubkey_to_hash160(&basepoint, &pubkeyhash); + if (memcmp(pubkeyhash.u.u8, goal_pubkeyhash, 20) == 0) { + printf("bech32 : %s\n", address); + 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; + } + } + + printf("Could not find any basepoint matching the provided witness programm.\n" + "Are you sure that the channel used `option_static_remotekey` ?\n"); + return 1; +} + int main(int argc, char *argv[]) { const char *method; @@ -319,5 +392,16 @@ int main(int argc, char *argv[]) argv[5], argv[6]); } + if (streq(method, "guesstoremote")) { + /* 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_to_remote(argv[2], &node_id, atol(argv[4]), + argv[5], argv[6]); + } + show_usage(); }