Skip to content

Commit a2792d9

Browse files
committed
zombierecovery: make preparekeys CLN compatible
1 parent e29402e commit a2792d9

File tree

4 files changed

+125
-62
lines changed

4 files changed

+125
-62
lines changed

cmd/chantools/zombierecovery_makeoffer.go

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -183,22 +183,6 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
183183
}
184184
}
185185

186-
// If we're only matching, we can stop here.
187-
if c.MatchOnly {
188-
ourPubKeys, err := parseKeys(keys1.Node1.MultisigKeys)
189-
if err != nil {
190-
return fmt.Errorf("error parsing their keys: %w", err)
191-
}
192-
193-
theirPubKeys, err := parseKeys(keys2.Node2.MultisigKeys)
194-
if err != nil {
195-
return fmt.Errorf("error parsing our keys: %w", err)
196-
}
197-
return matchKeys(
198-
keys1.Channels, ourPubKeys, theirPubKeys, chainParams,
199-
)
200-
}
201-
202186
// Make sure one of the nodes is ours.
203187
_, pubKey, _, err := lnd.DeriveKey(
204188
extendedKey, lnd.IdentityPath(chainParams), chainParams,
@@ -277,6 +261,11 @@ func (c *zombieRecoveryMakeOfferCommand) Execute(_ *cobra.Command,
277261
return err
278262
}
279263

264+
// If we're only matching, we can stop here.
265+
if c.MatchOnly {
266+
return nil
267+
}
268+
280269
// Let's prepare the PSBT.
281270
packet, err := psbt.NewFromUnsignedTx(wire.NewMsgTx(2))
282271
if err != nil {

cmd/chantools/zombierecovery_makeoffer_test.go

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,37 @@ import (
99
"github.com/stretchr/testify/require"
1010
)
1111

12-
var (
13-
key1Bytes, _ = hex.DecodeString(
14-
"0201943d78d61c8ad50ba57164830f536c156d8d89d979448bef3e67f564" +
15-
"ea0ab6",
16-
)
17-
key1, _ = btcec.ParsePubKey(key1Bytes)
18-
key2Bytes, _ = hex.DecodeString(
19-
"038b88de18064024e9da4dfc9c804283b3077a265dcd73ad3615b50badcb" +
20-
"debd5b",
21-
)
22-
key2, _ = btcec.ParsePubKey(key2Bytes)
23-
addr = "bc1qp5jnhnavt32fjwhnf5ttpvvym7e0syp79q5l9skz545q62d8u2uq05" +
24-
"ul63"
25-
)
26-
2712
func TestMatchScript(t *testing.T) {
28-
ok, _, _, err := matchScript(addr, key1, key2, &chaincfg.MainNetParams)
29-
require.NoError(t, err)
30-
require.True(t, ok)
13+
testCases := []struct {
14+
key1 string
15+
key2 string
16+
addr string
17+
params *chaincfg.Params
18+
}{{
19+
key1: "0201943d78d61c8ad50ba57164830f536c156d8d89d979448bef3e67f564ea0ab6",
20+
key2: "038b88de18064024e9da4dfc9c804283b3077a265dcd73ad3615b50badcbdebd5b",
21+
addr: "bc1qp5jnhnavt32fjwhnf5ttpvvym7e0syp79q5l9skz545q62d8u2uq05ul63",
22+
params: &chaincfg.MainNetParams,
23+
}, {
24+
key1: "03585d8e760bd0925da67d9c22a69dcad9f51f90a39f9a681971268555975ea30d",
25+
key2: "0326a2171c97673cc8cd7a04a043f0224c59591fc8c9de320a48f7c9b68ab0ae2b",
26+
addr: "bcrt1qhcn39q6jc0krkh9va230y2z6q96zadt8fhxw3erv92fzlrw83cyq40nwek",
27+
params: &chaincfg.RegressionNetParams,
28+
}}
29+
30+
for _, tc := range testCases {
31+
key1Bytes, err := hex.DecodeString(tc.key1)
32+
require.NoError(t, err)
33+
key1, err := btcec.ParsePubKey(key1Bytes)
34+
require.NoError(t, err)
35+
36+
key2Bytes, err := hex.DecodeString(tc.key2)
37+
require.NoError(t, err)
38+
key2, err := btcec.ParsePubKey(key2Bytes)
39+
require.NoError(t, err)
40+
41+
ok, _, _, err := matchScript(tc.addr, key1, key2, tc.params)
42+
require.NoError(t, err)
43+
require.True(t, ok)
44+
}
3145
}

cmd/chantools/zombierecovery_preparekeys.go

Lines changed: 87 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import (
99
"os"
1010
"time"
1111

12+
"github.com/btcsuite/btcd/btcec/v2"
1213
"github.com/btcsuite/btcd/btcutil"
1314
"github.com/btcsuite/btcd/wire"
15+
"github.com/lightninglabs/chantools/cln"
1416
"github.com/lightninglabs/chantools/lnd"
17+
"github.com/lightningnetwork/lnd/keychain"
1518
"github.com/spf13/cobra"
1619
)
1720

@@ -25,6 +28,8 @@ type zombieRecoveryPrepareKeysCommand struct {
2528

2629
NumKeys uint32
2730

31+
HsmSecret string
32+
2833
rootKey *rootKey
2934
cmd *cobra.Command
3035
}
@@ -58,6 +63,12 @@ correct ones for the matched channels.`,
5863
&cc.NumKeys, "num_keys", numMultisigKeys, "the number of "+
5964
"multisig keys to derive",
6065
)
66+
cc.cmd.Flags().StringVar(
67+
&cc.HsmSecret, "hsm_secret", "", "the hex encoded HSM secret "+
68+
"to use for deriving the multisig keys for a CLN "+
69+
"node; obtain by running 'xxd -p -c32 "+
70+
"~/.lightning/bitcoin/hsm_secret'",
71+
)
6172

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

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

70-
extendedKey, err := c.rootKey.read()
71-
if err != nil {
72-
return fmt.Errorf("error reading root key: %w", err)
73-
}
74-
75-
err = lnd.CheckAddress(
81+
err := lnd.CheckAddress(
7682
c.PayoutAddr, chainParams, false, "payout", lnd.AddrTypeP2WKH,
7783
lnd.AddrTypeP2TR,
7884
)
@@ -98,26 +104,68 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
98104
return errors.New("invalid match file, node info missing")
99105
}
100106

101-
_, pubKey, _, err := lnd.DeriveKey(
102-
extendedKey, lnd.IdentityPath(chainParams), chainParams,
103-
)
107+
// Derive the keys for the node type, depending on the input flags.
108+
var signer lnd.ChannelSigner
109+
switch {
110+
case c.HsmSecret != "":
111+
secretBytes, err := hex.DecodeString(c.HsmSecret)
112+
if err != nil {
113+
return fmt.Errorf("error decoding HSM secret: %w", err)
114+
}
115+
116+
var hsmSecret [32]byte
117+
copy(hsmSecret[:], secretBytes)
118+
119+
signer = &cln.Signer{
120+
HsmSecret: hsmSecret,
121+
}
122+
123+
default:
124+
extendedKey, err := c.rootKey.read()
125+
if err != nil {
126+
return fmt.Errorf("error reading root key: %w", err)
127+
}
128+
signer = &lnd.Signer{
129+
ExtendedKey: extendedKey,
130+
ChainParams: chainParams,
131+
}
132+
}
133+
134+
nodePrivKey, err := signer.FetchPrivateKey(&keychain.KeyDescriptor{
135+
KeyLocator: keychain.KeyLocator{
136+
Family: keychain.KeyFamilyNodeKey,
137+
},
138+
})
104139
if err != nil {
105-
return fmt.Errorf("error deriving identity pubkey: %w", err)
140+
return fmt.Errorf("error deriving identity private key: %w",
141+
err)
106142
}
107143

144+
pubKey := nodePrivKey.PubKey()
108145
pubKeyStr := hex.EncodeToString(pubKey.SerializeCompressed())
109-
var nodeInfo *nodeInfo
146+
var ourNodeInfo, theirNodeInfo *nodeInfo
110147
switch {
111148
case match.Node1.PubKey != pubKeyStr && match.Node2.PubKey != pubKeyStr:
112-
return fmt.Errorf("derived pubkey %s from seed but that key "+
113-
"was not found in the match file %s", pubKeyStr,
149+
return fmt.Errorf("derived pubkey %s from seed but that "+
150+
"key was not found in the match file %s", pubKeyStr,
114151
c.MatchFile)
115152

116153
case match.Node1.PubKey == pubKeyStr:
117-
nodeInfo = match.Node1
154+
ourNodeInfo = match.Node1
155+
theirNodeInfo = match.Node2
118156

119157
default:
120-
nodeInfo = match.Node2
158+
ourNodeInfo = match.Node2
159+
theirNodeInfo = match.Node1
160+
}
161+
162+
theirNodeKeyBytes, err := hex.DecodeString(theirNodeInfo.PubKey)
163+
if err != nil {
164+
return fmt.Errorf("error decoding peer pubkey: %w", err)
165+
}
166+
theirNodeKey, err := btcec.ParsePubKey(theirNodeKeyBytes)
167+
if err != nil {
168+
return fmt.Errorf("error parsing peer pubkey: %w", err)
121169
}
122170

123171
// If there are any Simple Taproot channels, we need to generate some
@@ -132,6 +180,12 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
132180

133181
_, isP2TR := addr.(*btcutil.AddressTaproot)
134182
if isP2TR {
183+
lndSigner, ok := signer.(*lnd.Signer)
184+
if !ok {
185+
return errors.New("taproot channels not " +
186+
"supported for CLN ")
187+
}
188+
135189
chanPoint, err := wire.NewOutPointFromString(
136190
matchChannel.ChanPoint,
137191
)
@@ -147,12 +201,13 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
147201
}
148202

149203
nonces, err := lnd.GenerateMuSig2Nonces(
150-
extendedKey, randomness, chanPoint, chainParams,
204+
lndSigner.ExtendedKey, randomness, chanPoint,
205+
chainParams,
151206
nil,
152207
)
153208
if err != nil {
154-
return fmt.Errorf("error generating MuSig2 "+
155-
"nonces: %w", err)
209+
return fmt.Errorf("error generating "+
210+
"MuSig2 nonces: %w", err)
156211
}
157212

158213
matchChannel.MuSig2NonceRandomness = hex.EncodeToString(
@@ -166,21 +221,25 @@ func (c *zombieRecoveryPrepareKeysCommand) Execute(_ *cobra.Command,
166221

167222
// Derive all 2500 keys now, this might take a while.
168223
for index := range c.NumKeys {
169-
_, pubKey, _, err := lnd.DeriveKey(
170-
extendedKey, lnd.MultisigPath(chainParams, int(index)),
171-
chainParams,
172-
)
224+
privKey, err := signer.FetchPrivateKey(&keychain.KeyDescriptor{
225+
PubKey: theirNodeKey,
226+
KeyLocator: keychain.KeyLocator{
227+
Family: keychain.KeyFamilyMultiSig,
228+
Index: index,
229+
},
230+
})
173231
if err != nil {
174-
return fmt.Errorf("error deriving multisig pubkey: %w",
175-
err)
232+
return fmt.Errorf("error deriving funding private "+
233+
"key: %w", err)
176234
}
177235

178-
nodeInfo.MultisigKeys = append(
179-
nodeInfo.MultisigKeys,
180-
hex.EncodeToString(pubKey.SerializeCompressed()),
236+
fundingPubKey := privKey.PubKey()
237+
ourNodeInfo.MultisigKeys = append(
238+
ourNodeInfo.MultisigKeys,
239+
hex.EncodeToString(fundingPubKey.SerializeCompressed()),
181240
)
182241
}
183-
nodeInfo.PayoutAddr = c.PayoutAddr
242+
ourNodeInfo.PayoutAddr = c.PayoutAddr
184243

185244
// Write the result back into a new file.
186245
matchBytes, err := json.MarshalIndent(match, "", " ")

doc/chantools_zombierecovery_preparekeys.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ chantools zombierecovery preparekeys \
2727
```
2828
--bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag
2929
-h, --help help for preparekeys
30+
--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'
3031
--match_file string the match JSON file that was sent to both nodes by the match maker
3132
--num_keys uint32 the number of multisig keys to derive (default 2500)
3233
--payout_addr string the address where this node's rescued funds should be sent to, must be a P2WPKH (native SegWit) or P2TR (Taproot) address

0 commit comments

Comments
 (0)