mirror of
https://github.com/guggero/chantools.git
synced 2026-08-13 12:33:34 +02:00
multi: add bitcoin-descriptors to genimportscript
This commit is contained in:
parent
f35a469949
commit
9fadec97a7
5 changed files with 173 additions and 12 deletions
|
|
@ -17,6 +17,7 @@ const (
|
|||
FormatCli = "bitcoin-cli"
|
||||
FormatCliWatchOnly = "bitcoin-cli-watchonly"
|
||||
FormatImportwallet = "bitcoin-importwallet"
|
||||
FormatDescriptors = "bitcoin-descriptors"
|
||||
FormatElectrum = "electrum"
|
||||
)
|
||||
|
||||
|
|
@ -40,6 +41,9 @@ func ParseFormat(format string) (KeyExporter, error) {
|
|||
case FormatImportwallet:
|
||||
return &ImportWallet{}, nil
|
||||
|
||||
case FormatDescriptors:
|
||||
return &Descriptors{}, nil
|
||||
|
||||
case FormatElectrum:
|
||||
return &Electrum{}, nil
|
||||
|
||||
|
|
@ -179,19 +183,14 @@ func (c *CliWatchOnly) Format(hdKey *hdkeychain.ExtendedKey,
|
|||
if err != nil {
|
||||
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 := ""
|
||||
if params.Net == wire.TestNet || params.Net == wire.TestNet3 {
|
||||
flags = " -testnet"
|
||||
}
|
||||
return fmt.Sprintf("bitcoin-cli%s importpubkey %x \"%s/%d/%d/\" "+
|
||||
"false # addr=%s,%s,%s,%s", flags, pubKey.SerializeCompressed(),
|
||||
path, branch, index, addrP2PKH, addrP2WKH, addrNP2WKH,
|
||||
addrP2TR), nil
|
||||
"false # addr=%s,%s,%s", flags, pubKey.SerializeCompressed(),
|
||||
path, branch, index, addrP2PKH, addrP2WKH, addrNP2WKH), nil
|
||||
}
|
||||
|
||||
func (c *CliWatchOnly) Trailer(birthdayBlock uint32) string {
|
||||
|
|
@ -276,3 +275,40 @@ func (p *Electrum) Format(hdKey *hdkeychain.ExtendedKey,
|
|||
func (p *Electrum) Trailer(_ uint32) string {
|
||||
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))
|
||||
}
|
||||
|
|
|
|||
83
btc/descriptors.go
Normal file
83
btc/descriptors.go
Normal file
|
|
@ -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
|
||||
}
|
||||
27
btc/descriptors_test.go
Normal file
27
btc/descriptors_test.go
Normal file
|
|
@ -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:
|
||||
* 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
|
||||
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 importpubkey command. That means, only the public keys are
|
||||
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
|
||||
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.`,
|
||||
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 \
|
||||
--recoverywindow 5000`,
|
||||
RunE: cc.Execute,
|
||||
|
|
@ -58,7 +65,8 @@ The following script formats are currently supported:
|
|||
&cc.Format, "format", "bitcoin-importwallet", "format of the "+
|
||||
"generated import script; currently supported are: "+
|
||||
"bitcoin-importwallet, bitcoin-cli, "+
|
||||
"bitcoin-cli-watchonly and electrum",
|
||||
"bitcoin-cli-watchonly, bitcoin-descriptors and "+
|
||||
"electrum",
|
||||
)
|
||||
cc.cmd.Flags().BoolVar(
|
||||
&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:
|
||||
* 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
|
||||
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 importpubkey command. That means, only the public keys are
|
||||
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.
|
||||
* electrum: Creates a text output that contains one private key per line with
|
||||
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]
|
||||
|
|
@ -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
|
||||
--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
|
||||
--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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue