Skip to content

Commit

Permalink
tool/hsmtool: add a 'guesstoremote' command
Browse files Browse the repository at this point in the history
This, in the case of data loss on a channel with `option_static_remotekey`
negotiated, allows to likely (if the dbid is not unreasonable) recover
the funds from a remote unilateral close just with the hsm_secret.

Changelog-added: A new command, 'guesstoremote', is added to the hsmtool. It is meant to be used to recover funds after an unilateral close of a channel with `option_static_remotekey` enabled.
  • Loading branch information
darosior committed Nov 25, 2019
1 parent 8393d21 commit 73924d8
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 1 deletion.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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).
Expand Down
2 changes: 1 addition & 1 deletion tools/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
84 changes: 84 additions & 0 deletions tools/hsmtool.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/tal/path/path.h>
#include <ccan/str/str.h>
#include <common/bech32.h>
#include <common/derive_basepoints.h>
#include <common/node_id.h>
#include <common/type_to_string.h>
Expand All @@ -30,6 +31,8 @@ static void show_usage(void)
printf(" - encrypt <path/to/hsm_secret> <password>\n");
printf(" - dumpcommitments <node id> <channel dbid> <depth> "
"<path/to/hsm_secret> [hsm_secret password]\n");
printf(" - guesstoremote <P2WPKH address> <node id> <tries> "
"<path/to/hsm_secret> [hsm_secret password]\n");
exit(0);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}

0 comments on commit 73924d8

Please sign in to comment.