Implement parser.DeriveAddressDescriptors from xpub

This commit is contained in:
Martin Boehm 2019-01-28 18:29:12 +01:00
parent dafe19cf29
commit 986275bb76
6 changed files with 144 additions and 3 deletions

4
Gopkg.lock generated
View File

@ -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"

View File

@ -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")

View File

@ -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
}

View File

@ -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)
}
})
}
}

View File

@ -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.

View File

@ -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)
}