multi: add bitcoin-descriptors to genimportscript

pull/66/head
Oliver Gugger 1 year ago
parent f35a469949
commit 9fadec97a7
No known key found for this signature in database
GPG Key ID: 8E4256593F177720

@ -17,6 +17,7 @@ const (
FormatCli = "bitcoin-cli" FormatCli = "bitcoin-cli"
FormatCliWatchOnly = "bitcoin-cli-watchonly" FormatCliWatchOnly = "bitcoin-cli-watchonly"
FormatImportwallet = "bitcoin-importwallet" FormatImportwallet = "bitcoin-importwallet"
FormatDescriptors = "bitcoin-descriptors"
FormatElectrum = "electrum" FormatElectrum = "electrum"
) )
@ -40,6 +41,9 @@ func ParseFormat(format string) (KeyExporter, error) {
case FormatImportwallet: case FormatImportwallet:
return &ImportWallet{}, nil return &ImportWallet{}, nil
case FormatDescriptors:
return &Descriptors{}, nil
case FormatElectrum: case FormatElectrum:
return &Electrum{}, nil return &Electrum{}, nil
@ -179,19 +183,14 @@ func (c *CliWatchOnly) Format(hdKey *hdkeychain.ExtendedKey,
if err != nil { if err != nil {
return "", fmt.Errorf("could not create address: %w", err) return "", fmt.Errorf("could not create address: %w", err)
} }
addrP2TR, err := lnd.P2TRAddr(pubKey, params)
if err != nil {
return "", fmt.Errorf("could not create address: %w", err)
}
flags := "" flags := ""
if params.Net == wire.TestNet || params.Net == wire.TestNet3 { if params.Net == wire.TestNet || params.Net == wire.TestNet3 {
flags = " -testnet" flags = " -testnet"
} }
return fmt.Sprintf("bitcoin-cli%s importpubkey %x \"%s/%d/%d/\" "+ return fmt.Sprintf("bitcoin-cli%s importpubkey %x \"%s/%d/%d/\" "+
"false # addr=%s,%s,%s,%s", flags, pubKey.SerializeCompressed(), "false # addr=%s,%s,%s", flags, pubKey.SerializeCompressed(),
path, branch, index, addrP2PKH, addrP2WKH, addrNP2WKH, path, branch, index, addrP2PKH, addrP2WKH, addrNP2WKH), nil
addrP2TR), nil
} }
func (c *CliWatchOnly) Trailer(birthdayBlock uint32) string { func (c *CliWatchOnly) Trailer(birthdayBlock uint32) string {
@ -276,3 +275,40 @@ func (p *Electrum) Format(hdKey *hdkeychain.ExtendedKey,
func (p *Electrum) Trailer(_ uint32) string { func (p *Electrum) Trailer(_ uint32) string {
return "" return ""
} }
type Descriptors struct{}
func (d *Descriptors) Header() string {
return "# Paste the following lines into a command line window."
}
func (d *Descriptors) 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: %w", err)
}
wif, err := btcutil.NewWIF(privKey, params, true)
if err != nil {
return "", fmt.Errorf("could not encode WIF: %w", err)
}
np2wkh := makeDescriptor("sh(wpkh(%s))", wif.String())
p2wkh := makeDescriptor("wpkh(%s)", wif.String())
p2tr := makeDescriptor("tr(%s)", wif.String())
return fmt.Sprintf("bitcoin-cli importdescriptors '[%s,%s,%s]'",
np2wkh, p2wkh, p2tr), nil
}
func (d *Descriptors) Trailer(birthdayBlock uint32) string {
return fmt.Sprintf("bitcoin-cli rescanblockchain %d\n", birthdayBlock)
}
func makeDescriptor(format, wif string) string {
descriptor := fmt.Sprintf(format, wif)
return fmt.Sprintf("{\"desc\":\"%s\",\"timestamp\":\"now\"}",
DescriptorSumCreate(descriptor))
}

@ -0,0 +1,83 @@
package btc
import (
"strings"
)
var (
inputCharset = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ" +
"&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\\\"\\\\ "
checksumCharset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"
generator = []uint64{
0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a,
0x644d626ffd,
}
)
func descriptorSumPolymod(symbols []uint64) uint64 {
chk := uint64(1)
for _, value := range symbols {
top := chk >> 35
chk = (chk&0x7ffffffff)<<5 ^ value
for i := 0; i < 5; i++ {
if (top>>i)&1 != 0 {
chk ^= generator[i]
}
}
}
return chk
}
func descriptorSumExpand(s string) []uint64 {
groups := []uint64{}
symbols := []uint64{}
for _, c := range s {
v := strings.IndexRune(inputCharset, c)
if v < 0 {
return nil
}
symbols = append(symbols, uint64(v&31))
groups = append(groups, uint64(v>>5))
if len(groups) == 3 {
symbols = append(
symbols, groups[0]*9+groups[1]*3+groups[2],
)
groups = []uint64{}
}
}
if len(groups) == 1 {
symbols = append(symbols, groups[0])
} else if len(groups) == 2 {
symbols = append(symbols, groups[0]*3+groups[1])
}
return symbols
}
func DescriptorSumCreate(s string) string {
symbols := append(descriptorSumExpand(s), 0, 0, 0, 0, 0, 0, 0, 0)
checksum := descriptorSumPolymod(symbols) ^ 1
builder := strings.Builder{}
for i := 0; i < 8; i++ {
builder.WriteByte(checksumCharset[(checksum>>(5*(7-i)))&31])
}
return s + "#" + builder.String()
}
func DescriptorSumCheck(s string, require bool) bool {
if !strings.Contains(s, "#") {
return !require
}
if s[len(s)-9] != '#' {
return false
}
for _, c := range s[len(s)-8:] {
if !strings.ContainsRune(checksumCharset, c) {
return false
}
}
symbols := append(
descriptorSumExpand(s[:len(s)-9]),
uint64(strings.Index(checksumCharset, s[len(s)-8:])),
)
return descriptorSumPolymod(symbols) == 1
}

@ -0,0 +1,27 @@
package btc
import (
"testing"
"github.com/stretchr/testify/require"
)
var testCases = []struct {
descriptor string
expectedSum string
}{{
descriptor: "addr(mkmZxiEcEd8ZqjQWVZuC6so5dFMKEFpN2j)",
expectedSum: "#02wpgw69",
}, {
descriptor: "tr(cRhCT5vC5NdnSrQ2Jrah6NPCcth41uT8DWFmA6uD8R4x2ufucnYX)",
expectedSum: "#gwfmkgga",
}}
func TestDescriptorSum(t *testing.T) {
for _, tc := range testCases {
sum := DescriptorSumCreate(tc.descriptor)
require.Equal(t, tc.descriptor+tc.expectedSum, sum)
DescriptorSumCheck(sum, true)
}
}

@ -41,7 +41,9 @@ imported into other software like bitcoind.
The following script formats are currently supported: The following script formats are currently supported:
* bitcoin-cli: Creates a list of bitcoin-cli importprivkey commands that can * bitcoin-cli: Creates a list of bitcoin-cli importprivkey commands that can
be used in combination with a bitcoind full node to recover the funds locked be used in combination with a bitcoind full node to recover the funds locked
in those private keys. in those private keys. NOTE: This will only work for legacy wallets and only
for legacy, p2sh-segwit and bech32 (p2pkh, np2wkh and p2wkh) addresses. Use
bitcoin-descriptors and a descriptor wallet for bech32m (p2tr).
* bitcoin-cli-watchonly: Does the same as bitcoin-cli but with the * bitcoin-cli-watchonly: Does the same as bitcoin-cli but with the
bitcoin-cli importpubkey command. That means, only the public keys are bitcoin-cli importpubkey command. That means, only the public keys are
imported into bitcoind to watch the UTXOs of those keys. The funds cannot be imported into bitcoind to watch the UTXOs of those keys. The funds cannot be
@ -49,7 +51,12 @@ The following script formats are currently supported:
* bitcoin-importwallet: Creates a text output that is compatible with * 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 * electrum: Creates a text output that contains one private key per line with
the address type as the prefix, the way Electrum expects them.`, the address type as the prefix, the way Electrum expects them.
* bitcoin-descriptors: Create a list of bitcoin-cli importdescriptors commands
that can be used in combination with a bitcoind full node that has a
descriptor wallet to recover the funds locked in those private keys.
NOTE: This will only work for descriptor wallets and only for
p2sh-segwit, bech32 and bech32m (np2wkh, p2wkh and p2tr) addresses.`,
Example: `chantools genimportscript --format bitcoin-cli \ Example: `chantools genimportscript --format bitcoin-cli \
--recoverywindow 5000`, --recoverywindow 5000`,
RunE: cc.Execute, RunE: cc.Execute,
@ -58,7 +65,8 @@ The following script formats are currently supported:
&cc.Format, "format", "bitcoin-importwallet", "format of the "+ &cc.Format, "format", "bitcoin-importwallet", "format of the "+
"generated import script; currently supported are: "+ "generated import script; currently supported are: "+
"bitcoin-importwallet, bitcoin-cli, "+ "bitcoin-importwallet, bitcoin-cli, "+
"bitcoin-cli-watchonly and electrum", "bitcoin-cli-watchonly, bitcoin-descriptors and "+
"electrum",
) )
cc.cmd.Flags().BoolVar( cc.cmd.Flags().BoolVar(
&cc.LndPaths, "lndpaths", false, "use all derivation paths "+ &cc.LndPaths, "lndpaths", false, "use all derivation paths "+

@ -11,7 +11,9 @@ imported into other software like bitcoind.
The following script formats are currently supported: The following script formats are currently supported:
* bitcoin-cli: Creates a list of bitcoin-cli importprivkey commands that can * bitcoin-cli: Creates a list of bitcoin-cli importprivkey commands that can
be used in combination with a bitcoind full node to recover the funds locked be used in combination with a bitcoind full node to recover the funds locked
in those private keys. in those private keys. NOTE: This will only work for legacy wallets and only
for legacy, p2sh-segwit and bech32 (p2pkh, np2wkh and p2wkh) addresses. Use
bitcoin-descriptors and a descriptor wallet for bech32m (p2tr).
* bitcoin-cli-watchonly: Does the same as bitcoin-cli but with the * bitcoin-cli-watchonly: Does the same as bitcoin-cli but with the
bitcoin-cli importpubkey command. That means, only the public keys are bitcoin-cli importpubkey command. That means, only the public keys are
imported into bitcoind to watch the UTXOs of those keys. The funds cannot be imported into bitcoind to watch the UTXOs of those keys. The funds cannot be
@ -20,6 +22,11 @@ The following script formats are currently supported:
bitcoind's importwallet command. bitcoind's importwallet command.
* electrum: Creates a text output that contains one private key per line with * electrum: Creates a text output that contains one private key per line with
the address type as the prefix, the way Electrum expects them. the address type as the prefix, the way Electrum expects them.
* bitcoin-descriptors: Create a list of bitcoin-cli importdescriptors commands
that can be used in combination with a bitcoind full node that has a
descriptor wallet to recover the funds locked in those private keys.
NOTE: This will only work for descriptor wallets and only for
p2sh-segwit, bech32 and bech32m (np2wkh, p2wkh and p2tr) addresses.
``` ```
chantools genimportscript [flags] chantools genimportscript [flags]
@ -37,7 +44,7 @@ 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 --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 --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, bitcoin-cli-watchonly and electrum (default "bitcoin-importwallet") --format string format of the generated import script; currently supported are: bitcoin-importwallet, bitcoin-cli, bitcoin-cli-watchonly, bitcoin-descriptors and electrum (default "bitcoin-importwallet")
-h, --help help for genimportscript -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 --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) --recoverywindow uint32 number of keys to scan per internal/external branch; output will consist of double this amount of keys (default 2500)

Loading…
Cancel
Save