Skip to content

Commit 4c92de5

Browse files
committed
Refactor genimportscript command
1 parent 2521b8d commit 4c92de5

File tree

4 files changed

+285
-156
lines changed

4 files changed

+285
-156
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,9 @@ Usage:
274274
275275
[genimportscript command options]
276276
--rootkey= BIP32 HD root key to use. Leave empty to prompt for lnd 24 word aezeed.
277-
--format= The format of the generated import script. Currently supported are: bitcoin-cli, bitcoin-cli-watchonly.
277+
--format= The format of the generated import script. Currently supported are: bitcoin-cli, bitcoin-cli-watchonly, bitcoin-importwallet.
278+
--lndpaths Use all derivation paths that lnd uses. Results in a large number of results. Cannot be used in conjunction with --derivationpath.
279+
--derivationpath= Use one specific derivation path. Specify the first levels of the derivation path before any internal/external branch. Cannot be used in conjunction with --lndpaths. (default m/84'/0'/0')
278280
--recoverywindow= The number of keys to scan per internal/external branch. The output will consist of double this amount of keys. (default 2500)
279281
--rescanfrom= The block number to rescan from. Will be set automatically from the wallet birthday if the lnd 24 word aezeed is entered. (default 500000)
280282
```

btc/bitcoind.go

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
package btc
2+
3+
import (
4+
"fmt"
5+
"io"
6+
"time"
7+
8+
"github.com/btcsuite/btcd/chaincfg"
9+
"github.com/btcsuite/btcd/txscript"
10+
"github.com/btcsuite/btcutil"
11+
"github.com/btcsuite/btcutil/hdkeychain"
12+
"github.com/guggero/chantools/lnd"
13+
)
14+
15+
const (
16+
FormatCli = "bitcoin-cli"
17+
FormatCliWatchOnly = "bitcoin-cli-watchonly"
18+
FormatImportwallet = "bitcoin-importwallet"
19+
)
20+
21+
type KeyExporter interface {
22+
Header() string
23+
Format(*hdkeychain.ExtendedKey, *chaincfg.Params, string, uint32,
24+
uint32) (string, error)
25+
Trailer(uint32) string
26+
}
27+
28+
// ParseFormat parses the given format name and returns its associated print
29+
// function.
30+
func ParseFormat(format string) KeyExporter {
31+
switch format {
32+
default:
33+
fallthrough
34+
35+
case FormatCli:
36+
return &Cli{}
37+
38+
case FormatCliWatchOnly:
39+
return &CliWatchOnly{}
40+
41+
case FormatImportwallet:
42+
return &ImportWallet{}
43+
}
44+
}
45+
46+
func ExportKeys(extendedKey *hdkeychain.ExtendedKey, strPaths []string,
47+
paths [][]uint32, params *chaincfg.Params, recoveryWindow,
48+
rescanFrom uint32, exporter KeyExporter, writer io.Writer) error {
49+
50+
_, _ = fmt.Fprintf(
51+
writer, "# Wallet dump created by chantools on %s\n",
52+
time.Now().UTC(),
53+
)
54+
_, _ = fmt.Fprintf(writer, "%s\n", exporter.Header())
55+
for idx, strPath := range strPaths {
56+
path := paths[idx]
57+
58+
// External branch first (<DerivationPath>/0/i).
59+
for i := uint32(0); i < recoveryWindow; i++ {
60+
path := append(path, 0, i)
61+
derivedKey, err := lnd.DeriveChildren(extendedKey, path)
62+
if err != nil {
63+
return err
64+
}
65+
result, err := exporter.Format(
66+
derivedKey, params, strPath, 0, i,
67+
)
68+
if err != nil {
69+
return err
70+
}
71+
_, _ = fmt.Fprintf(writer, "%s\n", result)
72+
}
73+
74+
// Now the internal branch (<DerivationPath>/1/i).
75+
for i := uint32(0); i < recoveryWindow; i++ {
76+
path := append(path, 1, i)
77+
derivedKey, err := lnd.DeriveChildren(extendedKey, path)
78+
if err != nil {
79+
return err
80+
}
81+
result, err := exporter.Format(
82+
derivedKey, params, strPath, 1, i,
83+
)
84+
if err != nil {
85+
return err
86+
}
87+
_, _ = fmt.Fprintf(writer, "%s\n", result)
88+
}
89+
}
90+
91+
_, _ = fmt.Fprintf(writer, "%s\n", exporter.Trailer(rescanFrom))
92+
return nil
93+
}
94+
95+
func SeedBirthdayToBlock(params *chaincfg.Params,
96+
birthdayTimestamp time.Time) uint32 {
97+
98+
var genesisTimestamp time.Time
99+
switch params.Name {
100+
case "mainnet":
101+
genesisTimestamp =
102+
chaincfg.MainNetParams.GenesisBlock.Header.Timestamp
103+
104+
case "testnet3":
105+
genesisTimestamp =
106+
chaincfg.TestNet3Params.GenesisBlock.Header.Timestamp
107+
108+
case "regtest", "simnet":
109+
return 0
110+
111+
default:
112+
panic(fmt.Errorf("unimplemented network %v", params.Name))
113+
}
114+
115+
// With the timestamps retrieved, we can estimate a block height by
116+
// taking the difference between them and dividing by the average block
117+
// time (10 minutes).
118+
return uint32(birthdayTimestamp.Sub(genesisTimestamp).Seconds() / 600)
119+
}
120+
121+
type Cli struct{}
122+
123+
func (c *Cli) Header() string {
124+
return "# Paste the following lines into a command line window."
125+
}
126+
127+
func (c *Cli) Format(hdKey *hdkeychain.ExtendedKey, params *chaincfg.Params,
128+
path string, branch, index uint32) (string, error) {
129+
130+
privKey, err := hdKey.ECPrivKey()
131+
if err != nil {
132+
return "", fmt.Errorf("could not derive private key: %v", err)
133+
}
134+
wif, err := btcutil.NewWIF(privKey, params, true)
135+
if err != nil {
136+
return "", fmt.Errorf("could not encode WIF: %v", err)
137+
}
138+
return fmt.Sprintf("bitcoin-cli importprivkey %s \"%s/%d/%d/\" false",
139+
wif.String(), path, branch, index), nil
140+
}
141+
142+
func (c *Cli) Trailer(birthdayBlock uint32) string {
143+
return fmt.Sprintf("bitcoin-cli rescanblockchain %d\n", birthdayBlock)
144+
}
145+
146+
type CliWatchOnly struct{}
147+
148+
func (c *CliWatchOnly) Header() string {
149+
return "# Paste the following lines into a command line window."
150+
}
151+
152+
func (c *CliWatchOnly) Format(hdKey *hdkeychain.ExtendedKey, _ *chaincfg.Params,
153+
path string, branch, index uint32) (string, error) {
154+
155+
pubKey, err := hdKey.ECPubKey()
156+
if err != nil {
157+
return "", fmt.Errorf("could not derive private key: %v", err)
158+
}
159+
return fmt.Sprintf("bitcoin-cli importpubkey %x \"%s/%d/%d/\" false",
160+
pubKey.SerializeCompressed(), path, branch, index), nil
161+
}
162+
163+
func (c *CliWatchOnly) Trailer(birthdayBlock uint32) string {
164+
return fmt.Sprintf("bitcoin-cli rescanblockchain %d\n", birthdayBlock)
165+
}
166+
167+
type ImportWallet struct{}
168+
169+
func (i *ImportWallet) Header() string {
170+
return "# Save this output to a file and use the importwallet " +
171+
"command of bitcoin core."
172+
}
173+
174+
func (i *ImportWallet) Format(hdKey *hdkeychain.ExtendedKey,
175+
params *chaincfg.Params, path string, branch, index uint32) (string,
176+
error) {
177+
178+
privKey, err := hdKey.ECPrivKey()
179+
if err != nil {
180+
return "", fmt.Errorf("could not derive private key: %v", err)
181+
}
182+
wif, err := btcutil.NewWIF(privKey, params, true)
183+
if err != nil {
184+
return "", fmt.Errorf("could not encode WIF: %v", err)
185+
}
186+
pubKey, err := hdKey.ECPubKey()
187+
if err != nil {
188+
return "", fmt.Errorf("could not derive private key: %v", err)
189+
}
190+
hash160 := btcutil.Hash160(pubKey.SerializeCompressed())
191+
addrP2PKH, err := btcutil.NewAddressPubKeyHash(hash160, params)
192+
if err != nil {
193+
return "", fmt.Errorf("could not create address: %v", err)
194+
}
195+
addrP2WKH, err := btcutil.NewAddressWitnessPubKeyHash(hash160, params)
196+
if err != nil {
197+
return "", fmt.Errorf("could not create address: %v", err)
198+
}
199+
script, err := txscript.PayToAddrScript(addrP2WKH)
200+
if err != nil {
201+
return "", fmt.Errorf("could not create script: %v", err)
202+
}
203+
addrNP2WKH, err := btcutil.NewAddressScriptHash(script, params)
204+
if err != nil {
205+
return "", fmt.Errorf("could not create address: %v", err)
206+
}
207+
208+
return fmt.Sprintf("%s 1970-01-01T00:00:01Z label=%s/%d/%d/ "+
209+
"# addr=%s,%s,%s", wif.String(), path, branch, index,
210+
addrP2PKH.EncodeAddress(), addrNP2WKH.EncodeAddress(),
211+
addrP2WKH.EncodeAddress(),
212+
), nil
213+
}
214+
215+
func (i *ImportWallet) Trailer(_ uint32) string {
216+
return ""
217+
}

0 commit comments

Comments
 (0)