From 79f65bb1a11723186da93e4052db5938de636ba7 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Tue, 23 Jan 2024 07:56:30 +0200 Subject: [PATCH] zombierecovery: add --matchonly flag to makeoffer With this commit we make it possible to just check whether two lists of public keys can match the given channels and derive the 2-of-2 multisig channel funding address. --- cmd/chantools/zombierecovery_makeoffer.go | 138 ++++++++++++++-------- 1 file changed, 92 insertions(+), 46 deletions(-) diff --git a/cmd/chantools/zombierecovery_makeoffer.go b/cmd/chantools/zombierecovery_makeoffer.go index 0683af3..63f3f8a 100644 --- a/cmd/chantools/zombierecovery_makeoffer.go +++ b/cmd/chantools/zombierecovery_makeoffer.go @@ -6,7 +6,6 @@ import ( "encoding/hex" "encoding/json" "fmt" - "io/ioutil" "os" "strconv" "strings" @@ -28,6 +27,8 @@ type zombieRecoveryMakeOfferCommand struct { Node2 string FeeRate uint32 + MatchOnly bool + rootKey *rootKey cmd *cobra.Command } @@ -64,6 +65,10 @@ a counter offer.`, &cc.FeeRate, "feerate", defaultFeeSatPerVByte, "fee rate to "+ "use for the sweep transaction in sat/vByte", ) + cc.cmd.Flags().BoolVar( + &cc.MatchOnly, "matchonly", false, "only match the keys, "+ + "don't create an offer", + ) cc.rootKey = newRootKey(cc.cmd, "signing the offer") @@ -82,12 +87,12 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command, c.FeeRate = defaultFeeSatPerVByte } - node1Bytes, err := ioutil.ReadFile(c.Node1) + node1Bytes, err := os.ReadFile(c.Node1) if err != nil { return fmt.Errorf("error reading node1 key file %s: %w", c.Node1, err) } - node2Bytes, err := ioutil.ReadFile(c.Node2) + node2Bytes, err := os.ReadFile(c.Node2) if err != nil { return fmt.Errorf("error reading node2 key file %s: %w", c.Node2, err) @@ -153,6 +158,22 @@ 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, @@ -206,52 +227,19 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command, return fmt.Errorf("payout address missing") } - ourPubKeys := make([]*btcec.PublicKey, len(ourKeys)) - theirPubKeys := make([]*btcec.PublicKey, len(theirKeys)) - for idx, pubKeyHex := range ourKeys { - ourPubKeys[idx], err = pubKeyFromHex(pubKeyHex) - if err != nil { - return fmt.Errorf("error parsing our pubKey: %w", err) - } - } - for idx, pubKeyHex := range theirKeys { - theirPubKeys[idx], err = pubKeyFromHex(pubKeyHex) - if err != nil { - return fmt.Errorf("error parsing their pubKey: %w", err) - } + ourPubKeys, err := parseKeys(ourKeys) + if err != nil { + return fmt.Errorf("error parsing their keys: %w", err) } - // Loop through all channels and all keys now, this will definitely take - // a while. -channelLoop: - for _, channel := range keys1.Channels { - for ourKeyIndex, ourKey := range ourPubKeys { - for _, theirKey := range theirPubKeys { - match, witnessScript, err := matchScript( - channel.Address, ourKey, theirKey, - chainParams, - ) - if err != nil { - return fmt.Errorf("error matching "+ - "keys to script: %w", err) - } - - if match { - channel.ourKeyIndex = uint32(ourKeyIndex) - channel.ourKey = ourKey - channel.theirKey = theirKey - channel.witnessScript = witnessScript - - log.Infof("Found keys for channel %s", - channel.ChanPoint) - - continue channelLoop - } - } - } + theirPubKeys, err := parseKeys(theirKeys) + if err != nil { + return fmt.Errorf("error parsing our keys: %w", err) + } - return fmt.Errorf("didn't find matching multisig keys for "+ - "channel %s", channel.ChanPoint) + err = matchKeys(keys1.Channels, ourPubKeys, theirPubKeys, chainParams) + if err != nil { + return err } // Let's now sum up the tally of how much of the rescued funds should @@ -444,6 +432,64 @@ channelLoop: return nil } +// parseKeys parses a list of string keys into public keys. +func parseKeys(keys []string) ([]*btcec.PublicKey, error) { + pubKeys := make([]*btcec.PublicKey, 0, len(keys)) + for _, key := range keys { + pubKey, err := pubKeyFromHex(key) + if err != nil { + return nil, err + } + pubKeys = append(pubKeys, pubKey) + } + + return pubKeys, nil +} + +// matchKeys tries to match the keys from the two nodes. It updates the channels +// with the correct keys and witness scripts. +func matchKeys(channels []*channel, ourPubKeys, theirPubKeys []*btcec.PublicKey, + chainParams *chaincfg.Params) error { + + // Loop through all channels and all keys now, this will definitely take + // a while. +channelLoop: + for _, channel := range channels { + for ourKeyIndex, ourKey := range ourPubKeys { + for _, theirKey := range theirPubKeys { + match, witnessScript, err := matchScript( + channel.Address, ourKey, theirKey, + chainParams, + ) + if err != nil { + return fmt.Errorf("error matching "+ + "keys to script: %w", err) + } + + if match { + channel.ourKeyIndex = uint32(ourKeyIndex) + channel.ourKey = ourKey + channel.theirKey = theirKey + channel.witnessScript = witnessScript + + log.Infof("Found keys for channel %s: "+ + "our key %x, their key %x", + channel.ChanPoint, + ourKey.SerializeCompressed(), + theirKey.SerializeCompressed()) + + continue channelLoop + } + } + } + + return fmt.Errorf("didn't find matching multisig keys for "+ + "channel %s", channel.ChanPoint) + } + + return nil +} + func matchScript(address string, key1, key2 *btcec.PublicKey, params *chaincfg.Params) (bool, []byte, error) {