From ce3cc2ceefcc3dbca3e1ca96752f7d4e5aa6dc5c Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Sun, 29 Aug 2021 22:04:05 +0200 Subject: [PATCH] genimportscript: add Electrum format, write result to file --- btc/bitcoind.go | 37 ++++++++++++++++++++++++++++++++ cmd/chantools/genimportscript.go | 29 +++++++++++++++++++++---- cmd/chantools/root.go | 2 +- doc/chantools_fakechanbackup.md | 2 +- doc/chantools_genimportscript.md | 5 ++++- lnd/hdkeychain.go | 12 +---------- 6 files changed, 69 insertions(+), 18 deletions(-) diff --git a/btc/bitcoind.go b/btc/bitcoind.go index c09f238..be02268 100644 --- a/btc/bitcoind.go +++ b/btc/bitcoind.go @@ -3,6 +3,7 @@ package btc import ( "fmt" "io" + "strings" "time" "github.com/btcsuite/btcd/chaincfg" @@ -16,6 +17,7 @@ const ( FormatCli = "bitcoin-cli" FormatCliWatchOnly = "bitcoin-cli-watchonly" FormatImportwallet = "bitcoin-importwallet" + FormatElectrum = "electrum" ) type KeyExporter interface { @@ -40,6 +42,9 @@ func ParseFormat(format string) KeyExporter { case FormatImportwallet: return &ImportWallet{} + + case FormatElectrum: + return &Electrum{} } } @@ -215,3 +220,35 @@ func (i *ImportWallet) Format(hdKey *hdkeychain.ExtendedKey, func (i *ImportWallet) Trailer(_ uint32) string { return "" } + +type Electrum struct{} + +func (p *Electrum) Header() string { + return "# Copy the content of this file (without this line) into " + + "Electrum." +} + +func (p *Electrum) Format(hdKey *hdkeychain.ExtendedKey, + params *chaincfg.Params, path string, branch, index uint32) (string, + error) { + + privKey, err := hdKey.ECPrivKey() + if err != nil { + return "", fmt.Errorf("could not derive private key: %v", err) + } + wif, err := btcutil.NewWIF(privKey, params, true) + if err != nil { + return "", fmt.Errorf("could not encode WIF: %v", err) + } + + prefix := "p2wpkh" + if strings.HasPrefix(path, lnd.WalletBIP49DerivationPath) { + prefix = "p2wpkh-p2sh" + } + + return fmt.Sprintf("%s:%s", prefix, wif.String()), nil +} + +func (p *Electrum) Trailer(_ uint32) string { + return "" +} diff --git a/cmd/chantools/genimportscript.go b/cmd/chantools/genimportscript.go index f9d297a..e302200 100644 --- a/cmd/chantools/genimportscript.go +++ b/cmd/chantools/genimportscript.go @@ -21,6 +21,7 @@ type genImportScriptCommand struct { DerivationPath string RecoveryWindow uint32 RescanFrom uint32 + Stdout bool rootKey *rootKey cmd *cobra.Command @@ -46,7 +47,9 @@ The following script formats are currently supported: imported into bitcoind to watch the UTXOs of those keys. The funds cannot be spent that way as they are watch-only. * bitcoin-importwallet: Creates a text output that is compatible with - bitcoind's importwallet command.`, + bitcoind's importwallet command. +* electrum: Creates a text output that contains one private key per line with + the address type as the prefix, the way Electrum expects them.`, Example: `chantools genimportscript --format bitcoin-cli \ --recoverywindow 5000`, RunE: cc.Execute, @@ -54,8 +57,8 @@ The following script formats are currently supported: cc.cmd.Flags().StringVar( &cc.Format, "format", "bitcoin-importwallet", "format of the "+ "generated import script; currently supported are: "+ - "bitcoin-importwallet, bitcoin-cli and "+ - "bitcoin-cli-watchonly", + "bitcoin-importwallet, bitcoin-cli, "+ + "bitcoin-cli-watchonly and electrum", ) cc.cmd.Flags().BoolVar( &cc.LndPaths, "lndpaths", false, "use all derivation paths "+ @@ -79,6 +82,9 @@ The following script formats are currently supported: "from the wallet birthday if the lnd 24 word aezeed "+ "is entered", ) + cc.cmd.Flags().BoolVar( + &cc.Stdout, "stdout", false, "write generated import script "+ + "to standard out instead of writing it to a file") cc.rootKey = newRootKey(cc.cmd, "decrypting the backup") @@ -137,10 +143,25 @@ func (c *genImportScriptCommand) Execute(_ *cobra.Command, _ []string) error { } } + writer := os.Stdout + if !c.Stdout { + fileName := fmt.Sprintf("results/genimportscript-%s.txt", + time.Now().Format("2006-01-02-15-04-05")) + log.Infof("Writing import script with format '%s' to %s", + c.Format, fileName) + + var err error + writer, err = os.Create(fileName) + if err != nil { + return fmt.Errorf("error creating result file %s: %v", + fileName, err) + } + } + exporter := btc.ParseFormat(c.Format) err = btc.ExportKeys( extendedKey, strPaths, paths, chainParams, c.RecoveryWindow, - c.RescanFrom, exporter, os.Stdout, + c.RescanFrom, exporter, writer, ) if err != nil { return fmt.Errorf("error exporting keys: %v", err) diff --git a/cmd/chantools/root.go b/cmd/chantools/root.go index 8592cfa..16bad0a 100644 --- a/cmd/chantools/root.go +++ b/cmd/chantools/root.go @@ -26,7 +26,7 @@ import ( const ( defaultAPIURL = "https://blockstream.info/api" - version = "0.9.2" + version = "0.9.3" na = "n/a" Commit = "" diff --git a/doc/chantools_fakechanbackup.md b/doc/chantools_fakechanbackup.md index 3f77ecd..8024238 100644 --- a/doc/chantools_fakechanbackup.md +++ b/doc/chantools_fakechanbackup.md @@ -61,7 +61,7 @@ chantools fakechanbackup --from_channel_graph lncli_describegraph.json \ --channelpoint string funding transaction outpoint of the channel to rescue (:) as it is displayed on 1ml.com --from_channel_graph string the full LN channel graph in the JSON format that the 'lncli describegraph' returns -h, --help help for fakechanbackup - --multi_file string the fake channel backup file to create (default "results/fake-2021-08-29-18-21-11.backup") + --multi_file string the fake channel backup file to create (default "results/fake-2021-08-29-22-04-34.backup") --remote_node_addr string the remote node connection information in the format pubkey@host:port --rootkey string BIP32 HD root key of the wallet to use for encrypting the backup; leave empty to prompt for lnd 24 word aezeed --short_channel_id string the short channel ID in the format xx diff --git a/doc/chantools_genimportscript.md b/doc/chantools_genimportscript.md index 1c3726d..6e48556 100644 --- a/doc/chantools_genimportscript.md +++ b/doc/chantools_genimportscript.md @@ -18,6 +18,8 @@ The following script formats are currently supported: spent that way as they are watch-only. * bitcoin-importwallet: Creates a text output that is compatible with bitcoind's importwallet command. +* electrum: Creates a text output that contains one private key per line with + the address type as the prefix, the way Electrum expects them. ``` chantools genimportscript [flags] @@ -35,12 +37,13 @@ chantools genimportscript --format bitcoin-cli \ ``` --bip39 read a classic BIP39 seed and passphrase from the terminal instead of asking for lnd seed format or providing the --rootkey flag --derivationpath string 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 - --format string format of the generated import script; currently supported are: bitcoin-importwallet, bitcoin-cli and bitcoin-cli-watchonly (default "bitcoin-importwallet") + --format string format of the generated import script; currently supported are: bitcoin-importwallet, bitcoin-cli, bitcoin-cli-watchonly and electrum (default "bitcoin-importwallet") -h, --help help for genimportscript --lndpaths use all derivation paths that lnd used; results in a large number of results; cannot be used in conjunction with --derivationpath --recoverywindow uint32 number of keys to scan per internal/external branch; output will consist of double this amount of keys (default 2500) --rescanfrom uint32 block number to rescan from; will be set automatically from the wallet birthday if the lnd 24 word aezeed is entered (default 500000) --rootkey string BIP32 HD root key of the wallet to use for decrypting the backup; leave empty to prompt for lnd 24 word aezeed + --stdout write generated import script to standard out instead of writing it to a file ``` ### Options inherited from parent commands diff --git a/lnd/hdkeychain.go b/lnd/hdkeychain.go index fc344d7..32ad65f 100644 --- a/lnd/hdkeychain.go +++ b/lnd/hdkeychain.go @@ -157,19 +157,9 @@ func AllDerivationPaths(params *chaincfg.Params) ([]string, [][]uint32, error) { ) } pathStrings := []string{ - "m/44'/0'/0'", - "m/49'/0'/0'", + WalletBIP49DerivationPath, WalletDefaultDerivationPath, - mkPath(keychain.KeyFamilyMultiSig), - mkPath(keychain.KeyFamilyRevocationBase), - mkPath(keychain.KeyFamilyHtlcBase), mkPath(keychain.KeyFamilyPaymentBase), - mkPath(keychain.KeyFamilyDelayBase), - mkPath(keychain.KeyFamilyRevocationRoot), - mkPath(keychain.KeyFamilyNodeKey), - mkPath(keychain.KeyFamilyStaticBackup), - mkPath(keychain.KeyFamilyTowerSession), - mkPath(keychain.KeyFamilyTowerID), } paths := make([][]uint32, len(pathStrings)) for idx, path := range pathStrings {