Implement parser.DeriveAddressDescriptors from xpub
This commit is contained in:
parent
dafe19cf29
commit
986275bb76
4
Gopkg.lock
generated
4
Gopkg.lock
generated
@ -94,8 +94,8 @@
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "github.com/martinboehm/btcutil"
|
||||
packages = [".","base58","bech32","chaincfg","txscript"]
|
||||
revision = "613fec26904062ae125fb073762af3a77c77b6c7"
|
||||
packages = [".","base58","bech32","chaincfg","hdkeychain","txscript"]
|
||||
revision = "9b332d8046124a83bab2830696e8ebddaf3f1788"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
|
||||
@ -267,6 +267,11 @@ func (p *BaseParser) UnpackTx(buf []byte) (*Tx, uint32, error) {
|
||||
return &tx, pt.Height, nil
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptors is unsupported
|
||||
func (p *BaseParser) DeriveAddressDescriptors(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
}
|
||||
|
||||
// EthereumTypeGetErc20FromTx is unsupported
|
||||
func (p *BaseParser) EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error) {
|
||||
return nil, errors.New("Not supported")
|
||||
|
||||
@ -8,10 +8,12 @@ import (
|
||||
"math/big"
|
||||
|
||||
vlq "github.com/bsm/go-vlq"
|
||||
"github.com/juju/errors"
|
||||
"github.com/martinboehm/btcd/blockchain"
|
||||
"github.com/martinboehm/btcd/wire"
|
||||
"github.com/martinboehm/btcutil"
|
||||
"github.com/martinboehm/btcutil/chaincfg"
|
||||
"github.com/martinboehm/btcutil/hdkeychain"
|
||||
"github.com/martinboehm/btcutil/txscript"
|
||||
)
|
||||
|
||||
@ -23,6 +25,9 @@ type BitcoinParser struct {
|
||||
*bchain.BaseParser
|
||||
Params *chaincfg.Params
|
||||
OutputScriptToAddressesFunc OutputScriptToAddressesFunc
|
||||
XPubMagic uint32
|
||||
XPubMagicSegwitP2sh uint32
|
||||
XPubMagicSegwitNative uint32
|
||||
}
|
||||
|
||||
// NewBitcoinParser returns new BitcoinParser instance
|
||||
@ -32,7 +37,10 @@ func NewBitcoinParser(params *chaincfg.Params, c *Configuration) *BitcoinParser
|
||||
BlockAddressesToKeep: c.BlockAddressesToKeep,
|
||||
AmountDecimalPoint: 8,
|
||||
},
|
||||
Params: params,
|
||||
Params: params,
|
||||
XPubMagic: c.XPubMagic,
|
||||
XPubMagicSegwitP2sh: c.XPubMagicSegwitP2sh,
|
||||
XPubMagicSegwitNative: c.XPubMagicSegwitNative,
|
||||
}
|
||||
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
|
||||
return p
|
||||
@ -266,3 +274,54 @@ func (p *BitcoinParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
|
||||
|
||||
return tx, height, nil
|
||||
}
|
||||
|
||||
func (p *BitcoinParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchain.AddressDescriptor, error) {
|
||||
var a btcutil.Address
|
||||
var err error
|
||||
if extKey.Version() == p.XPubMagicSegwitP2sh {
|
||||
// redeemScript <witness version: OP_0><len pubKeyHash: 20><20-byte-pubKeyHash>
|
||||
pubKeyHash := btcutil.Hash160(extKey.PubKeyBytes())
|
||||
redeemScript := make([]byte, len(pubKeyHash)+2)
|
||||
redeemScript[0] = 0
|
||||
redeemScript[1] = byte(len(pubKeyHash))
|
||||
copy(redeemScript[2:], pubKeyHash)
|
||||
hash := btcutil.Hash160(redeemScript)
|
||||
a, err = btcutil.NewAddressScriptHashFromHash(hash, p.Params)
|
||||
} else if extKey.Version() == p.XPubMagicSegwitNative {
|
||||
a, err = btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(extKey.PubKeyBytes()), p.Params)
|
||||
} else {
|
||||
// default to P2PKH address
|
||||
a, err = extKey.Address(p.Params)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return txscript.PayToAddrScript(a)
|
||||
}
|
||||
|
||||
// DeriveAddressDescriptors derives address descriptors from given xpub
|
||||
func (p *BitcoinParser) DeriveAddressDescriptors(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
||||
if toIndex <= fromIndex {
|
||||
return nil, errors.New("toIndex<=fromIndex")
|
||||
}
|
||||
extKey, err := hdkeychain.NewKeyFromString(xpub)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
changeExtKey, err := extKey.Child(change)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
|
||||
for index := fromIndex; index < toIndex; index++ {
|
||||
indexExtKey, err := changeExtKey.Child(index)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ad, nil
|
||||
}
|
||||
|
||||
@ -417,3 +417,75 @@ func Test_UnpackTx(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_DeriveAddressDescriptors(t *testing.T) {
|
||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||
type args struct {
|
||||
xpub string
|
||||
change uint32
|
||||
fromIndex uint32
|
||||
toIndex uint32
|
||||
parser *BitcoinParser
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "m/44'/0'/0'",
|
||||
args: args{
|
||||
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA"},
|
||||
},
|
||||
{
|
||||
name: "m/49'/0'/0'",
|
||||
args: args{
|
||||
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf"},
|
||||
},
|
||||
{
|
||||
name: "m/84'/0'/0'",
|
||||
args: args{
|
||||
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||
change: 0,
|
||||
fromIndex: 0,
|
||||
toIndex: 1,
|
||||
parser: btcMainParser,
|
||||
},
|
||||
want: []string{"bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := tt.args.parser.DeriveAddressDescriptors(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("DeriveAddressDescriptors() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
gotAddresses := make([]string, len(got))
|
||||
for i, ad := range got {
|
||||
aa, _, err := tt.args.parser.GetAddressesFromAddrDesc(ad)
|
||||
if err != nil || len(aa) != 1 {
|
||||
t.Errorf("DeriveAddressDescriptors() got incorrect address descriptor %v, error %v", ad, err)
|
||||
return
|
||||
}
|
||||
gotAddresses[i] = aa[0]
|
||||
}
|
||||
if !reflect.DeepEqual(gotAddresses, tt.want) {
|
||||
t.Errorf("DeriveAddressDescriptors() = %v, want %v", gotAddresses, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,6 +51,9 @@ type Configuration struct {
|
||||
AddressFormat string `json:"address_format"`
|
||||
SupportsEstimateFee bool `json:"supports_estimate_fee"`
|
||||
SupportsEstimateSmartFee bool `json:"supports_estimate_smart_fee"`
|
||||
XPubMagic uint32 `json:"xpub_magic,omitempty"`
|
||||
XPubMagicSegwitP2sh uint32 `json:"xpub_magic_segwit_p2sh,omitempty"`
|
||||
XPubMagicSegwitNative uint32 `json:"xpub_magic_segwit_native,omitempty"`
|
||||
}
|
||||
|
||||
// NewBitcoinRPC returns new BitcoinRPC instance.
|
||||
|
||||
@ -263,6 +263,8 @@ type BlockChainParser interface {
|
||||
PackBlockHash(hash string) ([]byte, error)
|
||||
UnpackBlockHash(buf []byte) (string, error)
|
||||
ParseBlock(b []byte) (*Block, error)
|
||||
// xpub
|
||||
DeriveAddressDescriptors(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error)
|
||||
// EthereumType specific
|
||||
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user