Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions common/hsm_secret.c
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
#include "config.h"
#include <assert.h>
#include <ccan/crypto/hkdf_sha256/hkdf_sha256.h>
#include <ccan/mem/mem.h>
#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/str/str.h>
#include <common/errcode.h>
#include <common/hsm_secret.h>
#include <common/memleak.h>
#include <common/node_id.h>
#include <common/utils.h>
#include <errno.h>
#include <sys/stat.h>
Expand All @@ -28,6 +30,26 @@
#define PASSPHRASE_HASH_LEN 32
#define HSM_SECRET_PLAIN_SIZE 32

void derive_channel_seed(struct secret *channel_seed,
const u8 hsm_secret[32],
const struct node_id *peer_id,
u64 dbid)
{
struct secret channel_base;
u8 input[sizeof(peer_id->k) + sizeof(dbid)];

/* These labels and the native-endian dbid are historical and must
* never change: existing channel keys depend on them. */
hkdf_sha256(&channel_base, sizeof(channel_base), NULL, 0,
hsm_secret, 32, "peer seed", strlen("peer seed"));
memcpy(input, peer_id->k, sizeof(peer_id->k));
BUILD_ASSERT(sizeof(peer_id->k) == PUBKEY_CMPR_LEN);
memcpy(input + PUBKEY_CMPR_LEN, &dbid, sizeof(dbid));
hkdf_sha256(channel_seed, sizeof(*channel_seed),
input, sizeof(input), &channel_base, sizeof(channel_base),
"per-peer seed", strlen("per-peer seed"));
}

/* Helper function to validate a mnemonic string */
enum hsm_secret_error validate_mnemonic(const char *mnemonic)
{
Expand Down
8 changes: 8 additions & 0 deletions common/hsm_secret.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,14 @@
#include <sodium.h>
#include <sys/types.h>

struct node_id;

/* Derive the seed for one channel from the first 32 bytes of hsm_secret. */
void derive_channel_seed(struct secret *channel_seed,
const u8 hsm_secret[32],
const struct node_id *peer_id,
u64 dbid);

/* Length constants for encrypted HSM secret files */
#define HS_HEADER_LEN crypto_secretstream_xchacha20poly1305_HEADERBYTES
#define HS_CIPHERTEXT_LEN \
Expand Down
38 changes: 2 additions & 36 deletions devtools/checkchannels.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,51 +9,17 @@
#include <ccan/tal/grab_file/grab_file.h>
#include <ccan/tal/path/path.h>
#include <common/configdir.h>
#include <common/hsm_secret.h>
#include <common/node_id.h>
#include <common/utils.h>
#include <inttypes.h>
#include <sqlite3.h>

static void hsm_channel_secret_base(struct secret *channel_seed_base,
const struct secret *hsm_secret)
{
hkdf_sha256(channel_seed_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"));
}

static void get_channel_seed(const struct secret *hsm_secret,
const struct node_id *peer_id, u64 dbid,
struct secret *channel_seed)
{
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. */
hsm_channel_secret_base(&channel_base, hsm_secret);
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));
derive_channel_seed(channel_seed, hsm_secret->data, peer_id, dbid);
}

struct keys {
Expand Down
83 changes: 83 additions & 0 deletions doc/lightning-hsmtool.8.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@ of the specified channel with the specified peer,
identified by the channel database index.
Specify *password* if the `hsm_secret_path` is encrypted.

**dumpcommitments** **--point-only** *node\_id* *channel\_dbid* *commitment\_number* *hsm\_secret\_path*

Show only the per-commitment point of commitment *commitment\_number*
of the specified channel.
Unlike the form above, this prints no secrets, so the result is safe to
give to the peer of that channel.

**guesstoremote** *p2wpkh* *node\_id* *max\_channel\_dbid* *hsm\_secret\_path* \[*password*\]

Brute-force the private key to our funds from a remote unilateral close
Expand All @@ -62,6 +69,82 @@ If *commitment\_point* is omitted, then the channel is assumed to have been
negotiated with `option_static_remotekey`;
otherwise, *commitment\_point* is the remote per-commitment point.

**recoverhtlc** *bundle\_path* *destination* *hsm\_secret\_path*

Sign transactions which spend our HTLC outputs of a commitment
transaction which the peer broadcast after we lost channel state.
In that case **lightningd** logs `Unknown commitment #N, recovering our
funds!`: it recovers our main output, but it cannot find the HTLC outputs
without the peer's per-commitment point.

Nothing is broadcast, and no private key is printed.
Each signed transaction pays the address *destination*, and is printed
with a description of what it spends, so you can check it and then
broadcast it yourself.

*bundle\_path* is a JSON file describing the commitment transaction and
the HTLCs to recover from it:

```json
{
"version": 1,
"network": "bitcoin",
"peer_id": "02...",
"channel_dbid": 123,
"remote_htlc_basepoint": "03...",
"commitment_point": "03...",
"commitment_tx": "0200...",
"feerate_perkw": 2500,
"htlcs": [
{"direction": "out", "payment_hash": "...", "cltv_expiry": 952295},
{"direction": "in", "preimage": "...", "cltv_expiry": 952300}
]
}
```

* *version* is always 1.
* *network* is the network name, such as `bitcoin`, `testnet4`, `signet`
or `regtest`.
* *peer\_id* and *channel\_dbid* are the node id of the peer and the `id`
of the channel in the `channels` table of our database.
* *remote\_htlc\_basepoint* is `htlc_basepoint_remote` in the same table.
The peer knows it too: it is their own HTLC basepoint.
* *commitment\_point* is the peer's per-commitment point for the
commitment they broadcast, and only the peer has it.
A Core Lightning peer can print it, and nothing secret, with
`lightning-hsmtool dumpcommitments --point-only <our node id> <their
channel dbid> N <their hsm_secret>`, where *N* is the number in our
`Unknown commitment #N` log message.
* *commitment\_tx* is the hex of the commitment transaction, as printed by
`bitcoin-cli getrawtransaction`.
* *feerate\_perkw* is optional, in satoshi per 1000 weight: 250 is
1 sat/vB. The default is 2500.
* Each of *htlcs* with *direction* `out` is an HTLC which we offered: it
needs the *payment\_hash* and *cltv\_expiry*, and we take it back once
it has expired.
Each with *direction* `in` is an HTLC which the peer offered: it needs
the *preimage*, and must confirm before the peer takes it back at
*cltv\_expiry*. Its *cltv\_expiry* and *payment\_hash* are optional.

The values from our database can be read from a copy of it, for example:

```
sqlite3 lightningd.sqlite3 "SELECT c.id, hex(p.node_id), \
hex(c.htlc_basepoint_remote) FROM channels c \
JOIN peers p ON p.id = c.peer_id"
```

A transaction is only signed for an HTLC whose script, rebuilt from these
values, is paid by an output of *commitment\_tx*: a wrong value cannot
produce a wrong spend.
An HTLC without such an output is skipped with a message, and the others
are still recovered. An HTLC too small to have an output in the
commitment transaction cannot be recovered.
Parts of one payment which have identical scripts are spent by a single
transaction.
Both anchor and non-anchor channels are supported; Elements networks are
not.

**generatehsm** *hsm\_secret\_path* \[*lang* *seed\_phrase* \[*passphrase*\]\]
Generates a new hsm\_secret using BIP39. If lang, seed\_phrase and optional passphrase are not provided they will be prompted for. *lang* can be "en" (English), "es" (Spanish), "fr" (French), "it" ("Italian"), "jp" (Japanese), "zhs" (Chinese Simplified) or "zht" ("Chinese Traditional"). Note that the seed phrase consists of multiple words, so should be surrounded by quotes.

Expand Down
38 changes: 1 addition & 37 deletions hsmd/libhsmd.c
Original file line number Diff line number Diff line change
Expand Up @@ -293,21 +293,6 @@ static void node_schnorrkey(secp256k1_keypair *node_keypair)
"Failed to derive keypair");
}

/*~ This secret is the basis for all per-channel secrets: the per-channel seeds
* will be generated by mixing in the dbid and the peer node_id. */
static void hsm_channel_secret_base(struct secret *channel_seed_base)
{
hkdf_sha256(channel_seed_base, sizeof(struct secret), NULL, 0,
secretstuff.bip32_seed, 32, /* Use first 32 bytes */
/*~ 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"));
}

/* This will derive pseudorandom secret Key from a derived key */
static u8 *handle_derive_secret(struct hsmd_client *c, const u8 *msg_in)
{
Expand All @@ -328,28 +313,7 @@ static u8 *handle_derive_secret(struct hsmd_client *c, const u8 *msg_in)
static void get_channel_seed(const struct node_id *peer_id, u64 dbid,
struct secret *channel_seed)
{
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. */
hsm_channel_secret_base(&channel_base);
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));
derive_channel_seed(channel_seed, secretstuff.bip32_seed, peer_id, dbid);
}

/* ~This stub implementation is overriden by fully validating signers
Expand Down
Loading
Loading