Skip to content

Commit 02b0551

Browse files
committed
zombierecovery: make signoffer CLN compatible
1 parent 5351357 commit 02b0551

File tree

3 files changed

+87
-88
lines changed

3 files changed

+87
-88
lines changed

cmd/chantools/signrescuefunding.go

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,8 @@ import (
66
"fmt"
77

88
"github.com/btcsuite/btcd/btcec/v2"
9-
"github.com/btcsuite/btcd/btcutil/hdkeychain"
109
"github.com/btcsuite/btcd/btcutil/psbt"
1110
"github.com/lightninglabs/chantools/lnd"
12-
"github.com/lightningnetwork/lnd/keychain"
1311
"github.com/spf13/cobra"
1412
)
1513

@@ -69,24 +67,10 @@ func (c *signRescueFundingCommand) Execute(_ *cobra.Command, _ []string) error {
6967
return fmt.Errorf("error decoding PSBT: %w", err)
7068
}
7169

72-
return signRescueFunding(extendedKey, packet, signer)
70+
return signRescueFunding(packet, signer)
7371
}
7472

75-
func signRescueFunding(rootKey *hdkeychain.ExtendedKey,
76-
packet *psbt.Packet, signer *lnd.Signer) error {
77-
78-
// First, we need to derive the correct branch from the local root key.
79-
localMultisig, err := lnd.DeriveChildren(rootKey, []uint32{
80-
lnd.HardenedKeyStart + uint32(keychain.BIP0043Purpose),
81-
lnd.HardenedKeyStart + chainParams.HDCoinType,
82-
lnd.HardenedKeyStart + uint32(keychain.KeyFamilyMultiSig),
83-
0,
84-
})
85-
if err != nil {
86-
return fmt.Errorf("could not derive local multisig key: %w",
87-
err)
88-
}
89-
73+
func signRescueFunding(packet *psbt.Packet, signer *lnd.Signer) error {
9074
// Now let's check that the packet has the expected proprietary key with
9175
// our pubkey that we need to sign with.
9276
if len(packet.Inputs) != 1 {
@@ -110,8 +94,12 @@ func signRescueFunding(rootKey *hdkeychain.ExtendedKey,
11094
}
11195

11296
// Now we can look up the local key and check the PSBT further, then
113-
// add our signature.
114-
localKeyDesc, err := findLocalMultisigKey(localMultisig, targetKey)
97+
// add our signature. This is NOT CLN compatible, as we'd need to
98+
// add the peer's public key as a command argument to pass into
99+
// FindMultisigKey.
100+
localKeyDesc, err := signer.FindMultisigKey(
101+
targetKey, nil, MaxChannelLookup,
102+
)
115103
if err != nil {
116104
return fmt.Errorf("could not find local multisig key: %w", err)
117105
}
@@ -153,36 +141,3 @@ func signRescueFunding(rootKey *hdkeychain.ExtendedKey,
153141

154142
return nil
155143
}
156-
157-
func findLocalMultisigKey(multisigBranch *hdkeychain.ExtendedKey,
158-
targetPubkey *btcec.PublicKey) (*keychain.KeyDescriptor, error) {
159-
160-
// Loop through the local multisig keys to find the target key.
161-
for index := range uint32(MaxChannelLookup) {
162-
currentKey, err := multisigBranch.DeriveNonStandard(index)
163-
if err != nil {
164-
return nil, fmt.Errorf("error deriving child key: %w",
165-
err)
166-
}
167-
168-
currentPubkey, err := currentKey.ECPubKey()
169-
if err != nil {
170-
return nil, fmt.Errorf("error deriving public key: %w",
171-
err)
172-
}
173-
174-
if !targetPubkey.IsEqual(currentPubkey) {
175-
continue
176-
}
177-
178-
return &keychain.KeyDescriptor{
179-
PubKey: currentPubkey,
180-
KeyLocator: keychain.KeyLocator{
181-
Family: keychain.KeyFamilyMultiSig,
182-
Index: index,
183-
},
184-
}, nil
185-
}
186-
187-
return nil, errors.New("no matching pubkeys found")
188-
}

cmd/chantools/zombierecovery_signoffer.go

Lines changed: 72 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,25 @@ package main
33
import (
44
"bufio"
55
"bytes"
6+
"encoding/hex"
67
"errors"
78
"fmt"
89
"os"
910

1011
"github.com/btcsuite/btcd/btcec/v2"
11-
"github.com/btcsuite/btcd/btcutil/hdkeychain"
1212
"github.com/btcsuite/btcd/btcutil/psbt"
1313
"github.com/btcsuite/btcd/txscript"
14+
"github.com/lightninglabs/chantools/cln"
1415
"github.com/lightninglabs/chantools/lnd"
15-
"github.com/lightningnetwork/lnd/keychain"
1616
"github.com/spf13/cobra"
1717
)
1818

1919
type zombieRecoverySignOfferCommand struct {
2020
Psbt string
2121

22+
HsmSecret string
23+
RemotePeer string
24+
2225
rootKey *rootKey
2326
cmd *cobra.Command
2427
}
@@ -40,6 +43,17 @@ peer to recover funds from one or more channels.`,
4043
&cc.Psbt, "psbt", "", "the base64 encoded PSBT that the other "+
4144
"party sent as an offer to rescue funds",
4245
)
46+
cc.cmd.Flags().StringVar(
47+
&cc.HsmSecret, "hsm_secret", "", "the hex encoded HSM secret "+
48+
"to use for deriving the multisig keys for a CLN "+
49+
"node; obtain by running 'xxd -p -c32 "+
50+
"~/.lightning/bitcoin/hsm_secret'",
51+
)
52+
cc.cmd.Flags().StringVar(
53+
&cc.RemotePeer, "remote_peer", "", "the hex encoded remote "+
54+
"peer node identity key, only required when running "+
55+
"'signoffer' on the CLN side",
56+
)
4357

4458
cc.rootKey = newRootKey(cc.cmd, "signing the offer")
4559

@@ -49,16 +63,6 @@ peer to recover funds from one or more channels.`,
4963
func (c *zombieRecoverySignOfferCommand) Execute(_ *cobra.Command,
5064
_ []string) error {
5165

52-
extendedKey, err := c.rootKey.read()
53-
if err != nil {
54-
return fmt.Errorf("error reading root key: %w", err)
55-
}
56-
57-
signer := &lnd.Signer{
58-
ExtendedKey: extendedKey,
59-
ChainParams: chainParams,
60-
}
61-
6266
// Decode the PSBT.
6367
packet, err := psbt.NewFromRawBytes(
6468
bytes.NewReader([]byte(c.Psbt)), true,
@@ -67,24 +71,56 @@ func (c *zombieRecoverySignOfferCommand) Execute(_ *cobra.Command,
6771
return fmt.Errorf("error decoding PSBT: %w", err)
6872
}
6973

70-
return signOffer(extendedKey, packet, signer)
71-
}
74+
var (
75+
signer lnd.ChannelSigner
76+
remoteNode *btcec.PublicKey
77+
)
78+
switch {
79+
case c.HsmSecret != "":
80+
secretBytes, err := hex.DecodeString(c.HsmSecret)
81+
if err != nil {
82+
return fmt.Errorf("error decoding HSM secret: %w", err)
83+
}
7284

73-
func signOffer(rootKey *hdkeychain.ExtendedKey,
74-
packet *psbt.Packet, signer *lnd.Signer) error {
85+
var hsmSecret [32]byte
86+
copy(hsmSecret[:], secretBytes)
7587

76-
// First, we need to derive the correct branch from the local root key.
77-
localMultisig, err := lnd.DeriveChildren(rootKey, []uint32{
78-
lnd.HardenedKeyStart + uint32(keychain.BIP0043Purpose),
79-
lnd.HardenedKeyStart + chainParams.HDCoinType,
80-
lnd.HardenedKeyStart + uint32(keychain.KeyFamilyMultiSig),
81-
0,
82-
})
83-
if err != nil {
84-
return fmt.Errorf("could not derive local multisig key: %w",
85-
err)
88+
if c.RemotePeer == "" {
89+
return errors.New("remote peer pubkey is required " +
90+
"when using the HSM secret")
91+
}
92+
93+
remoteNodeBytes, err := hex.DecodeString(c.RemotePeer)
94+
if err != nil {
95+
return fmt.Errorf("error decoding peer pubkey: %w", err)
96+
}
97+
remoteNode, err = btcec.ParsePubKey(remoteNodeBytes)
98+
if err != nil {
99+
return fmt.Errorf("error parsing peer pubkey: %w", err)
100+
}
101+
102+
signer = &cln.Signer{
103+
HsmSecret: hsmSecret,
104+
}
105+
106+
default:
107+
extendedKey, err := c.rootKey.read()
108+
if err != nil {
109+
return fmt.Errorf("error reading root key: %w", err)
110+
}
111+
112+
signer = &lnd.Signer{
113+
ExtendedKey: extendedKey,
114+
ChainParams: chainParams,
115+
}
86116
}
87117

118+
return signOffer(packet, signer, remoteNode)
119+
}
120+
121+
func signOffer(packet *psbt.Packet, signer lnd.ChannelSigner,
122+
peerPubKey *btcec.PublicKey) error {
123+
88124
// Now let's check that the packet has the expected proprietary key with
89125
// our pubkey that we need to sign with.
90126
if len(packet.Inputs) == 0 {
@@ -144,8 +180,8 @@ func signOffer(rootKey *hdkeychain.ExtendedKey,
144180

145181
// Now we can look up the local key and check the PSBT further,
146182
// then add our signature.
147-
localKeyDesc, err := findLocalMultisigKey(
148-
localMultisig, targetKey,
183+
localKeyDesc, err := signer.FindMultisigKey(
184+
targetKey, peerPubKey, MaxChannelLookup,
149185
)
150186
if err != nil {
151187
return fmt.Errorf("could not find local multisig key: "+
@@ -155,8 +191,14 @@ func signOffer(rootKey *hdkeychain.ExtendedKey,
155191
// If this is a Simple Taproot channel, we need to generate a
156192
// partial MuSig2 signature instead.
157193
if len(packet.Inputs[idx].MuSig2PartialSigs) > 0 {
194+
lndSigner, ok := signer.(*lnd.Signer)
195+
if !ok {
196+
return errors.New("taproot channels not yet " +
197+
"supported for CLN")
198+
}
199+
158200
err = muSig2PartialSign(
159-
signer, localKeyDesc, packet, idx,
201+
lndSigner, localKeyDesc, packet, idx,
160202
)
161203
if err != nil {
162204
return fmt.Errorf("error adding partial "+
@@ -187,7 +229,7 @@ func signOffer(rootKey *hdkeychain.ExtendedKey,
187229

188230
// We're almost done. Now we just need to make sure we can finalize and
189231
// extract the final TX.
190-
err = psbt.MaybeFinalizeAll(packet)
232+
err := psbt.MaybeFinalizeAll(packet)
191233
if err != nil {
192234
return fmt.Errorf("error finalizing PSBT: %w", err)
193235
}

doc/chantools_zombierecovery_signoffer.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,13 @@ chantools zombierecovery signoffer \
2121
### Options
2222

2323
```
24-
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
25-
-h, --help help for signoffer
26-
--psbt string the base64 encoded PSBT that the other party sent as an offer to rescue funds
27-
--rootkey string BIP32 HD root key of the wallet to use for signing the offer; leave empty to prompt for lnd 24 word aezeed
28-
--walletdb string read the seed/master root key to use for signing the offer from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
24+
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
25+
-h, --help help for signoffer
26+
--hsm_secret string 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'
27+
--psbt string the base64 encoded PSBT that the other party sent as an offer to rescue funds
28+
--remote_peer string the hex encoded remote peer node identity key, only required when running 'signoffer' on the CLN side
29+
--rootkey string BIP32 HD root key of the wallet to use for signing the offer; leave empty to prompt for lnd 24 word aezeed
30+
--walletdb string read the seed/master root key to use for signing the offer from an lnd wallet.db file instead of asking for a seed or providing the --rootkey flag
2931
```
3032

3133
### Options inherited from parent commands

0 commit comments

Comments
 (0)