Skip to content

Commit

Permalink
zombierecovery: make preparekeys CLN compatible
Browse files Browse the repository at this point in the history
  • Loading branch information
guggero committed Aug 19, 2024
1 parent f3edda7 commit f4b1923
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 57 deletions.
21 changes: 5 additions & 16 deletions cmd/chantools/zombierecovery_makeoffer.go
Original file line number Diff line number Diff line change
Expand Up @@ -181,22 +181,6 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
}
}

// If we're only matching, we can stop here.
if c.MatchOnly {
ourPubKeys, err := parseKeys(keys1.Node1.MultisigKeys)
if err != nil {
return fmt.Errorf("error parsing their keys: %w", err)
}

theirPubKeys, err := parseKeys(keys2.Node2.MultisigKeys)
if err != nil {
return fmt.Errorf("error parsing our keys: %w", err)
}
return matchKeys(
keys1.Channels, ourPubKeys, theirPubKeys, chainParams,
)
}

// Make sure one of the nodes is ours.
_, pubKey, _, err := lnd.DeriveKey(
extendedKey, lnd.IdentityPath(chainParams), chainParams,
Expand Down Expand Up @@ -275,6 +259,11 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
return err
}

// If we're only matching, we can stop here.
if c.MatchOnly {
return nil
}

// Let's prepare the PSBT.
packet, err := psbt.NewFromUnsignedTx(wire.NewMsgTx(2))
if err != nil {
Expand Down
50 changes: 32 additions & 18 deletions cmd/chantools/zombierecovery_makeoffer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,37 @@ import (
"github.com/stretchr/testify/require"
)

var (
key1Bytes, _ = hex.DecodeString(
"0201943d78d61c8ad50ba57164830f536c156d8d89d979448bef3e67f564" +
"ea0ab6",
)
key1, _ = btcec.ParsePubKey(key1Bytes)
key2Bytes, _ = hex.DecodeString(
"038b88de18064024e9da4dfc9c804283b3077a265dcd73ad3615b50badcb" +
"debd5b",
)
key2, _ = btcec.ParsePubKey(key2Bytes)
addr = "bc1qp5jnhnavt32fjwhnf5ttpvvym7e0syp79q5l9skz545q62d8u2uq05" +
"ul63"
)

func TestMatchScript(t *testing.T) {
ok, _, _, err := matchScript(addr, key1, key2, &chaincfg.MainNetParams)
require.NoError(t, err)
require.True(t, ok)
testCases := []struct {
key1 string
key2 string
addr string
params *chaincfg.Params
}{{
key1: "0201943d78d61c8ad50ba57164830f536c156d8d89d979448bef3e67f564ea0ab6",
key2: "038b88de18064024e9da4dfc9c804283b3077a265dcd73ad3615b50badcbdebd5b",
addr: "bc1qp5jnhnavt32fjwhnf5ttpvvym7e0syp79q5l9skz545q62d8u2uq05ul63",
params: &chaincfg.MainNetParams,
}, {
key1: "03585d8e760bd0925da67d9c22a69dcad9f51f90a39f9a681971268555975ea30d",
key2: "0326a2171c97673cc8cd7a04a043f0224c59591fc8c9de320a48f7c9b68ab0ae2b",
addr: "bcrt1qhcn39q6jc0krkh9va230y2z6q96zadt8fhxw3erv92fzlrw83cyq40nwek",
params: &chaincfg.RegressionNetParams,
}}

for _, tc := range testCases {
key1Bytes, err := hex.DecodeString(tc.key1)
require.NoError(t, err)
key1, err := btcec.ParsePubKey(key1Bytes)
require.NoError(t, err)

key2Bytes, err := hex.DecodeString(tc.key2)
require.NoError(t, err)
key2, err := btcec.ParsePubKey(key2Bytes)
require.NoError(t, err)

ok, _, err := matchScript(tc.addr, key1, key2, tc.params)

Check failure on line 41 in cmd/chantools/zombierecovery_makeoffer_test.go

View workflow job for this annotation

GitHub Actions / run unit tests

assignment mismatch: 3 variables but matchScript returns 4 values
require.NoError(t, err)
require.True(t, ok)
}
}
136 changes: 113 additions & 23 deletions cmd/chantools/zombierecovery_preparekeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,10 @@ import (
"os"
"time"

"github.com/btcsuite/btcd/btcec/v2"
"github.com/btcsuite/btcd/btcutil"
"github.com/btcsuite/btcd/wire"
"github.com/lightninglabs/chantools/cln"
"github.com/lightninglabs/chantools/lnd"
"github.com/spf13/cobra"
)
Expand All @@ -25,6 +27,8 @@ type zombieRecoveryPrepareKeysCommand struct {

NumKeys uint32

HsmSecret string

rootKey *rootKey
cmd *cobra.Command
}
Expand Down Expand Up @@ -58,6 +62,12 @@ correct ones for the matched channels.`,
&cc.NumKeys, "num_keys", numMultisigKeys, "the number of "+
"multisig keys to derive",
)
cc.cmd.Flags().StringVar(
&cc.HsmSecret, "hsm_secret", "", "the hex encoded HSM secret "+
"to use for deriving the multisig keys for a CLN "+
"node; obtain by running 'xxd -p -c32 "+
"~/.lightning/bitcoin/hsm_secret'",
)

cc.rootKey = newRootKey(cc.cmd, "deriving the multisig keys")

Expand All @@ -67,12 +77,7 @@ correct ones for the matched channels.`,
func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
_ []string) error {

extendedKey, err := c.rootKey.read()
if err != nil {
return fmt.Errorf("error reading root key: %w", err)
}

err = lnd.CheckAddress(
err := lnd.CheckAddress(
c.PayoutAddr, chainParams, false, "payout", lnd.AddrTypeP2WKH,
lnd.AddrTypeP2TR,
)
Expand All @@ -98,19 +103,51 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
return errors.New("invalid match file, node info missing")
}

// Derive the keys for the node type, depending on the input flags.
var pubKeyStr string
switch {
case c.HsmSecret != "":
pubKeyStr, err = c.clnDeriveKeys(&match)
default:
pubKeyStr, err = c.lndDeriveKeys(&match)
}
if err != nil {
return err
}

// Write the result back into a new file.
matchBytes, err := json.MarshalIndent(match, "", " ")
if err != nil {
return err
}

fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json",
time.Now().Format("2006-01-02"), pubKeyStr)
log.Infof("Writing result to %s", fileName)
return os.WriteFile(fileName, matchBytes, 0644)
}

func (c *zombieRecoveryPrepareKeysCommand) lndDeriveKeys(match *match) (string,
error) {

extendedKey, err := c.rootKey.read()
if err != nil {
return "", fmt.Errorf("error reading root key: %w", err)
}

_, pubKey, _, err := lnd.DeriveKey(
extendedKey, lnd.IdentityPath(chainParams), chainParams,
)
if err != nil {
return fmt.Errorf("error deriving identity pubkey: %w", err)
return "", fmt.Errorf("error deriving identity pubkey: %w", err)
}

pubKeyStr := hex.EncodeToString(pubKey.SerializeCompressed())
var nodeInfo *nodeInfo
switch {
case match.Node1.PubKey != pubKeyStr && match.Node2.PubKey != pubKeyStr:
return fmt.Errorf("derived pubkey %s from seed but that key "+
"was not found in the match file %s", pubKeyStr,
return "", fmt.Errorf("derived pubkey %s from seed but that "+
"key was not found in the match file %s", pubKeyStr,
c.MatchFile)

case match.Node1.PubKey == pubKeyStr:
Expand All @@ -126,7 +163,7 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
matchChannel := match.Channels[idx]
addr, err := lnd.ParseAddress(matchChannel.Address, chainParams)
if err != nil {
return fmt.Errorf("error parsing channel funding "+
return "", fmt.Errorf("error parsing channel funding "+
"address '%s': %w", matchChannel.Address, err)
}

Expand All @@ -136,23 +173,23 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
matchChannel.ChanPoint,
)
if err != nil {
return fmt.Errorf("error parsing channel "+
return "", fmt.Errorf("error parsing channel "+
"point %s: %w", matchChannel.ChanPoint,
err)
}

var randomness [32]byte
if _, err := rand.Read(randomness[:]); err != nil {
return err
return "", err
}

nonces, err := lnd.GenerateMuSig2Nonces(
extendedKey, randomness, chanPoint, chainParams,
nil,
)
if err != nil {
return fmt.Errorf("error generating MuSig2 "+
"nonces: %w", err)
return "", fmt.Errorf("error generating "+
"MuSig2 nonces: %w", err)
}

matchChannel.MuSig2NonceRandomness = hex.EncodeToString(
Expand All @@ -171,8 +208,8 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
chainParams,
)
if err != nil {
return fmt.Errorf("error deriving multisig pubkey: %w",
err)
return "", fmt.Errorf("error deriving multisig "+
"pubkey: %w", err)
}

nodeInfo.MultisigKeys = append(
Expand All @@ -182,14 +219,67 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
}
nodeInfo.PayoutAddr = c.PayoutAddr

// Write the result back into a new file.
matchBytes, err := json.MarshalIndent(match, "", " ")
return pubKeyStr, nil
}

func (c *zombieRecoveryPrepareKeysCommand) clnDeriveKeys(match *match) (string,
error) {

secretBytes, err := hex.DecodeString(c.HsmSecret)
if err != nil {
return err
return "", fmt.Errorf("error decoding HSM secret: %w", err)
}

fileName := fmt.Sprintf("results/preparedkeys-%s-%s.json",
time.Now().Format("2006-01-02"), pubKeyStr)
log.Infof("Writing result to %s", fileName)
return os.WriteFile(fileName, matchBytes, 0644)
var hsmSecret [32]byte
copy(hsmSecret[:], secretBytes)

nodePubKey, err := cln.NodeKey(hsmSecret)
if err != nil {
return "", fmt.Errorf("error deriving node pubkey: %w", err)
}

pubKeyStr := hex.EncodeToString(nodePubKey.SerializeCompressed())
var ourNodeInfo, theirNodeInfo *nodeInfo
switch {
case match.Node1.PubKey != pubKeyStr && match.Node2.PubKey != pubKeyStr:
return "", fmt.Errorf("derived pubkey %s from seed but that "+
"key was not found in the match file %s", pubKeyStr,
c.MatchFile)

case match.Node1.PubKey == pubKeyStr:
ourNodeInfo = match.Node1
theirNodeInfo = match.Node2

default:
ourNodeInfo = match.Node2
theirNodeInfo = match.Node1
}

theirNodeKeyBytes, err := hex.DecodeString(theirNodeInfo.PubKey)
if err != nil {
return "", fmt.Errorf("error decoding peer pubkey: %w", err)
}
theirNodeKey, err := btcec.ParsePubKey(theirNodeKeyBytes)
if err != nil {
return "", fmt.Errorf("error parsing peer pubkey: %w", err)
}

// Derive all 2500 keys now, this might take a while.
for index := range c.NumKeys {
pubKey, err := cln.FundingKey(
hsmSecret, theirNodeKey, uint64(index),
)
if err != nil {
return "", fmt.Errorf("error deriving multisig "+
"pubkey: %w", err)
}

ourNodeInfo.MultisigKeys = append(
ourNodeInfo.MultisigKeys,
hex.EncodeToString(pubKey.SerializeCompressed()),
)
}
ourNodeInfo.PayoutAddr = c.PayoutAddr

return pubKeyStr, nil
}

0 comments on commit f4b1923

Please sign in to comment.