How to recover funds from a force-closed Lnd lightning channel? I lost my Lnd channel backup in a boating accident, but I still have the mnemonic. I had one freshly opened channel to my own c-lightning node which has never been used. I force closed the channel from the c-lightning side.

The funds are now in a P2WPKH address which I've imported into Bitcoin Core. Now I need to figure out the private key in order to sweep it. This requires knowing how to derive it, and then how to dump it.

There is wallet utility pull request that I could tweak in order to dump the key I need once I know how to derive it.

I also know the funding transaction id, the channel id and all the other channel details c-lightning spits out with listfunds. 

Key derivation is explained in derivation.go:

m/1017'/coinType'/keyFamily'/0/index


The coinType for Bitcoin is 0, and I believe the key family is 3: "used in scripts that pay directly to us without any delay"

My guess is that the index is 0 since it was the first channel.

BOLT 3 then explains how to do tweak this key for a commitment transaction. Because the other side closed this channel, I need the to_remote output, which requires the remotepubkey.

pubkey = basepoint + SHA256(per_commitment_point || basepoint) * G 
privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)


The spec says "the remotepubkey uses the remote node's payment_basepoint". Is that the key we just derived?

So that leaves the per_commitment_point to be figured out, derived from per_commitment_secret. I'm confused where that comes from.

What Go incantation should I use to dump the private key I'm looking for?
In c-lightning the per commitment point is derived from the hsm_secret using per_commit_point from:


The node id / public key (can be found with listfunds, until the channel is purged some time after closing)
The channel database id (can be found with cat ~/.lightning/debug.log | grep $node_id), shown as chan #$node_id
The commitment number (0 when opening the channel, increases during fee negotiations and other channel updates, |  grep commit)


We care about the commitment point, not the secret, because the private key we need is derived as follows:

privkey = basepoint_secret + SHA256(per_commitment_point || basepoint)


I created a tool for c-lightning to dump this point (with help from @cdecker): https://github.com/ElementsProject/lightning/pull/3115

Back to Lnd... One easy place to dump a private key is in the lnwallet walletinfo command. Add something like this to commands.go:



basePointSecret, err := keyRing.DerivePrivKey(keychain.KeyDescriptor{
  KeyLocator: keychain.KeyLocator{
    Family: keychain.KeyFamilyPaymentBase,
    Index:  0, // Same commitment index as used in c-lightning 
  },
})

// Parse per_commitment_point dumped by c-lightning:
bytes, err := hex.DecodeString(".........")
commitPoint, err := btcec.ParsePubKey(bytes, btcec.S256())

// Obtain SHA256(per_commitment_point || basepoint)
h := sha256.New()
h.Write(commitPoint.SerializeCompressed())
h.Write(basePointSecret.PubKey().SerializeCompressed())
commitTweak := h.Sum(nil)

// Obtain and dump private key
tweakedPrivkey := TweakPrivKey(basePointSecret, commitTweak)
commitmentWif, err := btcutil.NewWIF(tweakedPrivkey, &amp;chaincfg.MainNetParams, true)

fmt.Printf("Dump commitment priv key: %s\n", commitmentWif)


Where TweakPrivKey() is lifted from script_utils.

Import the resulting private key into Bitcoin Core et voila!