Parse xpub descriptors
This commit is contained in:
parent
c4128e5c9c
commit
e500d6873d
70
api/xpub.go
70
api/xpub.go
@ -56,6 +56,7 @@ type xpubAddress struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type xpubData struct {
|
type xpubData struct {
|
||||||
|
descriptor *bchain.XpubDescriptor
|
||||||
gap int
|
gap int
|
||||||
accessed int64
|
accessed int64
|
||||||
basePath string
|
basePath string
|
||||||
@ -64,8 +65,7 @@ type xpubData struct {
|
|||||||
txCountEstimate uint32
|
txCountEstimate uint32
|
||||||
sentSat big.Int
|
sentSat big.Int
|
||||||
balanceSat big.Int
|
balanceSat big.Int
|
||||||
addresses []xpubAddress
|
addresses [][]xpubAddress
|
||||||
changeAddresses []xpubAddress
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) initXpubCache() {
|
func (w *Worker) initXpubCache() {
|
||||||
@ -201,7 +201,7 @@ func (w *Worker) xpubDerivedAddressBalance(data *xpubData, ad *xpubAddress) (boo
|
|||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpubAddress, gap int, change int, minDerivedIndex int, fork bool) (int, []xpubAddress, error) {
|
func (w *Worker) xpubScanAddresses(xd *bchain.XpubDescriptor, data *xpubData, addresses []xpubAddress, gap int, change uint32, minDerivedIndex int, fork bool) (int, []xpubAddress, error) {
|
||||||
// rescan known addresses
|
// rescan known addresses
|
||||||
lastUsed := 0
|
lastUsed := 0
|
||||||
for i := range addresses {
|
for i := range addresses {
|
||||||
@ -229,7 +229,7 @@ func (w *Worker) xpubScanAddresses(xpub string, data *xpubData, addresses []xpub
|
|||||||
if to < minDerivedIndex {
|
if to < minDerivedIndex {
|
||||||
to = minDerivedIndex
|
to = minDerivedIndex
|
||||||
}
|
}
|
||||||
descriptors, err := w.chainParser.DeriveAddressDescriptorsFromTo(xpub, uint32(change), uint32(from), uint32(to))
|
descriptors, err := w.chainParser.DeriveAddressDescriptorsFromTo(xd, change, uint32(from), uint32(to))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, nil, err
|
return 0, nil, err
|
||||||
}
|
}
|
||||||
@ -277,7 +277,7 @@ func (w *Worker) tokenFromXpubAddress(data *xpubData, ad *xpubAddress, changeInd
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, bool, error) {
|
func (w *Worker) getXpubData(xd *bchain.XpubDescriptor, page int, txsOnPage int, option AccountDetails, filter *AddressFilter, gap int) (*xpubData, uint32, bool, error) {
|
||||||
if w.chainType != bchain.ChainBitcoinType {
|
if w.chainType != bchain.ChainBitcoinType {
|
||||||
return nil, 0, false, ErrUnsupportedXpub
|
return nil, 0, false, ErrUnsupportedXpub
|
||||||
}
|
}
|
||||||
@ -296,7 +296,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
|
|||||||
gap++
|
gap++
|
||||||
var processedHash string
|
var processedHash string
|
||||||
cachedXpubsMux.Lock()
|
cachedXpubsMux.Lock()
|
||||||
data, inCache := cachedXpubs[xpub]
|
data, inCache := cachedXpubs[xd.XpubDescriptor]
|
||||||
cachedXpubsMux.Unlock()
|
cachedXpubsMux.Unlock()
|
||||||
// to load all data for xpub may take some time, do it in a loop to process a possible new block
|
// to load all data for xpub may take some time, do it in a loop to process a possible new block
|
||||||
for {
|
for {
|
||||||
@ -309,8 +309,11 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
|
|||||||
}
|
}
|
||||||
fork := false
|
fork := false
|
||||||
if !inCache || data.gap != gap {
|
if !inCache || data.gap != gap {
|
||||||
data = xpubData{gap: gap}
|
data = xpubData{
|
||||||
data.basePath, err = w.chainParser.DerivationBasePath(xpub)
|
gap: gap,
|
||||||
|
addresses: make([][]xpubAddress, len(xd.ChangeIndexes)),
|
||||||
|
}
|
||||||
|
data.basePath, err = w.chainParser.DerivationBasePath(xd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, 0, inCache, err
|
return nil, 0, inCache, err
|
||||||
}
|
}
|
||||||
@ -331,18 +334,16 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
|
|||||||
data.balanceSat = *new(big.Int)
|
data.balanceSat = *new(big.Int)
|
||||||
data.sentSat = *new(big.Int)
|
data.sentSat = *new(big.Int)
|
||||||
data.txCountEstimate = 0
|
data.txCountEstimate = 0
|
||||||
var lastUsedIndex int
|
var minDerivedIndex int
|
||||||
lastUsedIndex, data.addresses, err = w.xpubScanAddresses(xpub, &data, data.addresses, gap, 0, 0, fork)
|
for i, change := range xd.ChangeIndexes {
|
||||||
if err != nil {
|
minDerivedIndex, data.addresses[i], err = w.xpubScanAddresses(xd, &data, data.addresses[i], gap, change, minDerivedIndex, fork)
|
||||||
return nil, 0, inCache, err
|
if err != nil {
|
||||||
}
|
return nil, 0, inCache, err
|
||||||
_, data.changeAddresses, err = w.xpubScanAddresses(xpub, &data, data.changeAddresses, gap, 1, lastUsedIndex, fork)
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, 0, inCache, err
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if option >= AccountDetailsTxidHistory {
|
if option >= AccountDetailsTxidHistory {
|
||||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
for _, da := range data.addresses {
|
||||||
for i := range da {
|
for i := range da {
|
||||||
if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil {
|
if err = w.xpubCheckAndLoadTxids(&da[i], filter, bestheight, (page+1)*txsOnPage); err != nil {
|
||||||
return nil, 0, inCache, err
|
return nil, 0, inCache, err
|
||||||
@ -353,7 +354,7 @@ func (w *Worker) getXpubData(xpub string, page int, txsOnPage int, option Accoun
|
|||||||
}
|
}
|
||||||
data.accessed = time.Now().Unix()
|
data.accessed = time.Now().Unix()
|
||||||
cachedXpubsMux.Lock()
|
cachedXpubsMux.Lock()
|
||||||
cachedXpubs[xpub] = data
|
cachedXpubs[xd.XpubDescriptor] = data
|
||||||
cachedXpubsMux.Unlock()
|
cachedXpubsMux.Unlock()
|
||||||
return &data, bestheight, inCache, nil
|
return &data, bestheight, inCache, nil
|
||||||
}
|
}
|
||||||
@ -377,11 +378,14 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||||||
txids []string
|
txids []string
|
||||||
pg Paging
|
pg Paging
|
||||||
filtered bool
|
filtered bool
|
||||||
err error
|
|
||||||
uBalSat big.Int
|
uBalSat big.Int
|
||||||
unconfirmedTxs int
|
unconfirmedTxs int
|
||||||
)
|
)
|
||||||
data, bestheight, inCache, err := w.getXpubData(xpub, page, txsOnPage, option, filter, gap)
|
xd, err := w.chainParser.ParseXpub(xpub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, bestheight, inCache, err := w.getXpubData(xd, page, txsOnPage, option, filter, gap)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -410,7 +414,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||||||
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
if filter.ToHeight == 0 && !filter.OnlyConfirmed {
|
||||||
txmMap = make(map[string]*Tx)
|
txmMap = make(map[string]*Tx)
|
||||||
mempoolEntries := make(bchain.MempoolTxidEntries, 0)
|
mempoolEntries := make(bchain.MempoolTxidEntries, 0)
|
||||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
for _, da := range data.addresses {
|
||||||
for i := range da {
|
for i := range da {
|
||||||
ad := &da[i]
|
ad := &da[i]
|
||||||
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, true, 0, 0, maxInt)
|
newTxids, _, err := w.xpubGetAddressTxids(ad.addrDesc, true, 0, 0, maxInt)
|
||||||
@ -457,7 +461,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||||||
if option >= AccountDetailsTxidHistory {
|
if option >= AccountDetailsTxidHistory {
|
||||||
txcMap := make(map[string]bool)
|
txcMap := make(map[string]bool)
|
||||||
txc = make(xpubTxids, 0, 32)
|
txc = make(xpubTxids, 0, 32)
|
||||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
for _, da := range data.addresses {
|
||||||
for i := range da {
|
for i := range da {
|
||||||
ad := &da[i]
|
ad := &da[i]
|
||||||
for _, txid := range ad.txids {
|
for _, txid := range ad.txids {
|
||||||
@ -515,7 +519,7 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||||||
tokens = make([]Token, 0, 4)
|
tokens = make([]Token, 0, 4)
|
||||||
xpubAddresses = make(map[string]struct{})
|
xpubAddresses = make(map[string]struct{})
|
||||||
}
|
}
|
||||||
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
for ci, da := range data.addresses {
|
||||||
for i := range da {
|
for i := range da {
|
||||||
ad := &da[i]
|
ad := &da[i]
|
||||||
if ad.balance != nil {
|
if ad.balance != nil {
|
||||||
@ -549,14 +553,18 @@ func (w *Worker) GetXpubAddress(xpub string, page int, txsOnPage int, option Acc
|
|||||||
Tokens: tokens,
|
Tokens: tokens,
|
||||||
XPubAddresses: xpubAddresses,
|
XPubAddresses: xpubAddresses,
|
||||||
}
|
}
|
||||||
glog.Info("GetXpubAddress ", xpub[:xpubLogPrefix], ", cache ", inCache, ", ", len(data.addresses)+len(data.changeAddresses), " addresses, ", txCount, " txs, ", time.Since(start))
|
glog.Info("GetXpubAddress ", xpub[:xpubLogPrefix], ", cache ", inCache, ", ", txCount, " txs, ", time.Since(start))
|
||||||
return &addr, nil
|
return &addr, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetXpubUtxo returns unspent outputs for given xpub
|
// GetXpubUtxo returns unspent outputs for given xpub
|
||||||
func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, error) {
|
func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
data, _, inCache, err := w.getXpubData(xpub, 0, 1, AccountDetailsBasic, &AddressFilter{
|
xd, err := w.chainParser.ParseXpub(xpub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, _, inCache, err := w.getXpubData(xd, 0, 1, AccountDetailsBasic, &AddressFilter{
|
||||||
Vout: AddressFilterVoutOff,
|
Vout: AddressFilterVoutOff,
|
||||||
OnlyConfirmed: onlyConfirmed,
|
OnlyConfirmed: onlyConfirmed,
|
||||||
}, gap)
|
}, gap)
|
||||||
@ -564,7 +572,7 @@ func (w *Worker) GetXpubUtxo(xpub string, onlyConfirmed bool, gap int) (Utxos, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
r := make(Utxos, 0, 8)
|
r := make(Utxos, 0, 8)
|
||||||
for ci, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
for ci, da := range data.addresses {
|
||||||
for i := range da {
|
for i := range da {
|
||||||
ad := &da[i]
|
ad := &da[i]
|
||||||
onlyMempool := false
|
onlyMempool := false
|
||||||
@ -602,7 +610,11 @@ func (w *Worker) GetXpubBalanceHistory(xpub string, fromTimestamp, toTimestamp i
|
|||||||
if fromHeight >= toHeight {
|
if fromHeight >= toHeight {
|
||||||
return bhs, nil
|
return bhs, nil
|
||||||
}
|
}
|
||||||
data, _, inCache, err := w.getXpubData(xpub, 0, 1, AccountDetailsTxidHistory, &AddressFilter{
|
xd, err := w.chainParser.ParseXpub(xpub)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
data, _, inCache, err := w.getXpubData(xd, 0, 1, AccountDetailsTxidHistory, &AddressFilter{
|
||||||
Vout: AddressFilterVoutOff,
|
Vout: AddressFilterVoutOff,
|
||||||
OnlyConfirmed: true,
|
OnlyConfirmed: true,
|
||||||
FromHeight: fromHeight,
|
FromHeight: fromHeight,
|
||||||
@ -612,12 +624,12 @@ func (w *Worker) GetXpubBalanceHistory(xpub string, fromTimestamp, toTimestamp i
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
selfAddrDesc := make(map[string]struct{})
|
selfAddrDesc := make(map[string]struct{})
|
||||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
for _, da := range data.addresses {
|
||||||
for i := range da {
|
for i := range da {
|
||||||
selfAddrDesc[string(da[i].addrDesc)] = struct{}{}
|
selfAddrDesc[string(da[i].addrDesc)] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, da := range [][]xpubAddress{data.addresses, data.changeAddresses} {
|
for _, da := range data.addresses {
|
||||||
for i := range da {
|
for i := range da {
|
||||||
ad := &da[i]
|
ad := &da[i]
|
||||||
txids := ad.txids
|
txids := ad.txids
|
||||||
|
|||||||
@ -280,18 +280,23 @@ func (p *BaseParser) IsAddrDescIndexable(addrDesc AddressDescriptor) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ParseXpub is unsupported
|
||||||
|
func (p *BaseParser) ParseXpub(xpub string) (*XpubDescriptor, error) {
|
||||||
|
return nil, errors.New("Not supported")
|
||||||
|
}
|
||||||
|
|
||||||
// DerivationBasePath is unsupported
|
// DerivationBasePath is unsupported
|
||||||
func (p *BaseParser) DerivationBasePath(xpub string) (string, error) {
|
func (p *BaseParser) DerivationBasePath(descriptor *XpubDescriptor) (string, error) {
|
||||||
return "", errors.New("Not supported")
|
return "", errors.New("Not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeriveAddressDescriptors is unsupported
|
// DeriveAddressDescriptors is unsupported
|
||||||
func (p *BaseParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]AddressDescriptor, error) {
|
func (p *BaseParser) DeriveAddressDescriptors(descriptor *XpubDescriptor, change uint32, indexes []uint32) ([]AddressDescriptor, error) {
|
||||||
return nil, errors.New("Not supported")
|
return nil, errors.New("Not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeriveAddressDescriptorsFromTo is unsupported
|
// DeriveAddressDescriptorsFromTo is unsupported
|
||||||
func (p *BaseParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
|
func (p *BaseParser) DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error) {
|
||||||
return nil, errors.New("Not supported")
|
return nil, errors.New("Not supported")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
vlq "github.com/bsm/go-vlq"
|
vlq "github.com/bsm/go-vlq"
|
||||||
@ -360,28 +361,27 @@ func (p *BitcoinLikeParser) taprootAddrFromExtKey(extKey *hdkeychain.ExtendedKey
|
|||||||
return btcutil.NewAddressWitnessTaproot(b, p.Params)
|
return btcutil.NewAddressWitnessTaproot(b, p.Params)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BitcoinLikeParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey, bip string) (bchain.AddressDescriptor, error) {
|
func (p *BitcoinLikeParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey, descriptor *bchain.XpubDescriptor) (bchain.AddressDescriptor, error) {
|
||||||
var a btcutil.Address
|
var a btcutil.Address
|
||||||
var err error
|
var err error
|
||||||
if bip != "" {
|
switch descriptor.Type {
|
||||||
|
case bchain.P2PKH:
|
||||||
|
a, err = extKey.Address(p.Params)
|
||||||
|
case bchain.P2SHWPKH:
|
||||||
|
// 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)
|
||||||
|
case bchain.P2WPKH:
|
||||||
|
a, err = btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(extKey.PubKeyBytes()), p.Params)
|
||||||
|
case bchain.P2TR:
|
||||||
a, err = p.taprootAddrFromExtKey(extKey)
|
a, err = p.taprootAddrFromExtKey(extKey)
|
||||||
} else {
|
default:
|
||||||
version := extKey.Version()
|
return nil, errors.New("Unsupported xpub descriptor type")
|
||||||
if 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 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -389,33 +389,135 @@ func (p *BitcoinLikeParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey, b
|
|||||||
return txscript.PayToAddrScript(a)
|
return txscript.PayToAddrScript(a)
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseXpub(xpub string) (string, string) {
|
func (p *BitcoinLikeParser) xpubDescriptorFromXpub(xpub string) (*bchain.XpubDescriptor, error) {
|
||||||
r, _ := regexp.Compile(`tr\(\[\w+/(\d+)'/.*\](\w+)`)
|
var descriptor bchain.XpubDescriptor
|
||||||
match := r.FindStringSubmatch(xpub)
|
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
||||||
if len(match) == 3 {
|
if err != nil {
|
||||||
return match[2], match[1]
|
return nil, err
|
||||||
}
|
}
|
||||||
return xpub, ""
|
descriptor.Xpub = xpub
|
||||||
|
descriptor.XpubDescriptor = xpub
|
||||||
|
if extKey.Version() == p.XPubMagicSegwitP2sh {
|
||||||
|
descriptor.Type = bchain.P2SHWPKH
|
||||||
|
descriptor.Bip = "49"
|
||||||
|
} else if extKey.Version() == p.XPubMagicSegwitNative {
|
||||||
|
descriptor.Type = bchain.P2WPKH
|
||||||
|
descriptor.Bip = "84"
|
||||||
|
} else {
|
||||||
|
descriptor.Type = bchain.P2PKH
|
||||||
|
descriptor.Bip = "44"
|
||||||
|
}
|
||||||
|
descriptor.ChangeIndexes = []uint32{0, 1}
|
||||||
|
descriptor.ExtKey = extKey
|
||||||
|
return &descriptor, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
xpubDesriptorRegex *regexp.Regexp
|
||||||
|
typeSubexpIndex int
|
||||||
|
bipSubexpIndex int
|
||||||
|
xpubSubexpIndex int
|
||||||
|
changeSubexpIndex int
|
||||||
|
changeList1SubexpIndex int
|
||||||
|
changeList2SubexpIndex int
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
xpubDesriptorRegex, _ = regexp.Compile(`^(?P<type>(sh\(wpkh|wpkh|pk|pkh|wpkh|wsh|tr))\((\[\w+/(?P<bip>\d+)'/\d+'?/\d+'?\])?(?P<xpub>\w+)(/(({(?P<changelist1>\d+(,\d+)*)})|(<(?P<changelist2>\d+(;\d+)*)>)|(?P<change>\d+))/\*)?\)+`)
|
||||||
|
typeSubexpIndex = xpubDesriptorRegex.SubexpIndex("type")
|
||||||
|
bipSubexpIndex = xpubDesriptorRegex.SubexpIndex("bip")
|
||||||
|
xpubSubexpIndex = xpubDesriptorRegex.SubexpIndex("xpub")
|
||||||
|
changeList1SubexpIndex = xpubDesriptorRegex.SubexpIndex("changelist1")
|
||||||
|
changeList2SubexpIndex = xpubDesriptorRegex.SubexpIndex("changelist2")
|
||||||
|
changeSubexpIndex = xpubDesriptorRegex.SubexpIndex("change")
|
||||||
|
if changeSubexpIndex < 0 {
|
||||||
|
panic("Invalid bitcoinparser xpubDesriptorRegex")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseXpub parses xpub (or xpub descriptor) and returns XpubDescriptor
|
||||||
|
func (p *BitcoinLikeParser) ParseXpub(xpub string) (*bchain.XpubDescriptor, error) {
|
||||||
|
match := xpubDesriptorRegex.FindStringSubmatch(xpub)
|
||||||
|
if len(match) > changeSubexpIndex {
|
||||||
|
var descriptor bchain.XpubDescriptor
|
||||||
|
descriptor.XpubDescriptor = xpub
|
||||||
|
m := match[typeSubexpIndex]
|
||||||
|
switch m {
|
||||||
|
case "pkh":
|
||||||
|
descriptor.Type = bchain.P2PKH
|
||||||
|
descriptor.Bip = "44"
|
||||||
|
case "sh(wpkh":
|
||||||
|
descriptor.Type = bchain.P2SHWPKH
|
||||||
|
descriptor.Bip = "49"
|
||||||
|
case "wpkh":
|
||||||
|
descriptor.Type = bchain.P2WPKH
|
||||||
|
descriptor.Bip = "84"
|
||||||
|
case "tr":
|
||||||
|
descriptor.Type = bchain.P2TR
|
||||||
|
descriptor.Bip = "86"
|
||||||
|
default:
|
||||||
|
return nil, errors.Errorf("Xpub descriptor %s is not supported", m)
|
||||||
|
}
|
||||||
|
if len(match[bipSubexpIndex]) > 0 {
|
||||||
|
descriptor.Bip = match[bipSubexpIndex]
|
||||||
|
}
|
||||||
|
descriptor.Xpub = match[xpubSubexpIndex]
|
||||||
|
extKey, err := hdkeychain.NewKeyFromString(descriptor.Xpub, p.Params.Base58CksumHasher)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
descriptor.ExtKey = extKey
|
||||||
|
if len(match[changeSubexpIndex]) > 0 {
|
||||||
|
change, err := strconv.ParseUint(match[changeSubexpIndex], 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
descriptor.ChangeIndexes = []uint32{uint32(change)}
|
||||||
|
} else {
|
||||||
|
if len(match[changeList1SubexpIndex]) > 0 || len(match[changeList2SubexpIndex]) > 0 {
|
||||||
|
var changes []string
|
||||||
|
if len(match[changeList1SubexpIndex]) > 0 {
|
||||||
|
changes = strings.Split(match[changeList1SubexpIndex], ",")
|
||||||
|
} else {
|
||||||
|
changes = strings.Split(match[changeList2SubexpIndex], ";")
|
||||||
|
}
|
||||||
|
if len(changes) == 0 {
|
||||||
|
return nil, errors.New("Invalid xpub descriptor, cannot parse change")
|
||||||
|
}
|
||||||
|
descriptor.ChangeIndexes = make([]uint32, len(changes))
|
||||||
|
for i, ch := range changes {
|
||||||
|
change, err := strconv.ParseUint(ch, 10, 32)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
descriptor.ChangeIndexes[i] = uint32(change)
|
||||||
|
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// default to {0,1}
|
||||||
|
descriptor.ChangeIndexes = []uint32{0, 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return &descriptor, nil
|
||||||
|
}
|
||||||
|
return p.xpubDescriptorFromXpub(xpub)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeriveAddressDescriptors derives address descriptors from given xpub for listed indexes
|
// DeriveAddressDescriptors derives address descriptors from given xpub for listed indexes
|
||||||
func (p *BitcoinLikeParser) DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
func (p *BitcoinLikeParser) DeriveAddressDescriptors(descriptor *bchain.XpubDescriptor, change uint32, indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
||||||
parsedXpub, bip := parseXpub(xpub)
|
|
||||||
extKey, err := hdkeychain.NewKeyFromString(parsedXpub, p.Params.Base58CksumHasher)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
changeExtKey, err := extKey.Derive(change)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ad := make([]bchain.AddressDescriptor, len(indexes))
|
ad := make([]bchain.AddressDescriptor, len(indexes))
|
||||||
|
changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Derive(change)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
for i, index := range indexes {
|
for i, index := range indexes {
|
||||||
indexExtKey, err := changeExtKey.Derive(index)
|
indexExtKey, err := changeExtKey.Derive(index)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ad[i], err = p.addrDescFromExtKey(indexExtKey, bip)
|
ad[i], err = p.addrDescFromExtKey(indexExtKey, descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -424,16 +526,11 @@ func (p *BitcoinLikeParser) DeriveAddressDescriptors(xpub string, change uint32,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
|
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
|
||||||
func (p *BitcoinLikeParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
func (p *BitcoinLikeParser) DeriveAddressDescriptorsFromTo(descriptor *bchain.XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
||||||
if toIndex <= fromIndex {
|
if toIndex <= fromIndex {
|
||||||
return nil, errors.New("toIndex<=fromIndex")
|
return nil, errors.New("toIndex<=fromIndex")
|
||||||
}
|
}
|
||||||
parsedXpub, bip := parseXpub(xpub)
|
changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Derive(change)
|
||||||
extKey, err := hdkeychain.NewKeyFromString(parsedXpub, p.Params.Base58CksumHasher)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
changeExtKey, err := extKey.Derive(change)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -443,7 +540,7 @@ func (p *BitcoinLikeParser) DeriveAddressDescriptorsFromTo(xpub string, change u
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey, bip)
|
ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey, descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -452,13 +549,9 @@ func (p *BitcoinLikeParser) DeriveAddressDescriptorsFromTo(xpub string, change u
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DerivationBasePath returns base path of xpub
|
// DerivationBasePath returns base path of xpub
|
||||||
func (p *BitcoinLikeParser) DerivationBasePath(xpub string) (string, error) {
|
func (p *BitcoinLikeParser) DerivationBasePath(descriptor *bchain.XpubDescriptor) (string, error) {
|
||||||
parsedXpub, bip := parseXpub(xpub)
|
|
||||||
extKey, err := hdkeychain.NewKeyFromString(parsedXpub, p.Params.Base58CksumHasher)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
var c string
|
var c string
|
||||||
|
extKey := descriptor.ExtKey.(*hdkeychain.ExtendedKey)
|
||||||
cn := extKey.ChildNum()
|
cn := extKey.ChildNum()
|
||||||
if cn >= 0x80000000 {
|
if cn >= 0x80000000 {
|
||||||
cn -= 0x80000000
|
cn -= 0x80000000
|
||||||
@ -468,14 +561,5 @@ func (p *BitcoinLikeParser) DerivationBasePath(xpub string) (string, error) {
|
|||||||
if extKey.Depth() != 3 {
|
if extKey.Depth() != 3 {
|
||||||
return "unknown/" + c, nil
|
return "unknown/" + c, nil
|
||||||
}
|
}
|
||||||
if bip == "" {
|
return "m/" + descriptor.Bip + "'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
|
||||||
if extKey.Version() == p.XPubMagicSegwitP2sh {
|
|
||||||
bip = "49"
|
|
||||||
} else if extKey.Version() == p.XPubMagicSegwitNative {
|
|
||||||
bip = "84"
|
|
||||||
} else {
|
|
||||||
bip = "44"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return "m/" + bip + "'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -718,6 +718,212 @@ func TestUnpackTx(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestParseXpubDescriptors(t *testing.T) {
|
||||||
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
|
btcTestnetParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198})
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
xpub string
|
||||||
|
parser *BitcoinParser
|
||||||
|
want *bchain.XpubDescriptor
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "tpub",
|
||||||
|
xpub: "tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN",
|
||||||
|
parser: btcTestnetParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN",
|
||||||
|
Xpub: "tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN",
|
||||||
|
Type: bchain.P2PKH,
|
||||||
|
Bip: "44",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tr(tpub)",
|
||||||
|
xpub: "tr(tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN)",
|
||||||
|
parser: btcTestnetParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "tr(tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN)",
|
||||||
|
Xpub: "tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN",
|
||||||
|
Type: bchain.P2TR,
|
||||||
|
Bip: "86",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tr([5c9e228d/86'/1'/0']tpubD/{0,1,2}/*)#4rqwxvej",
|
||||||
|
xpub: "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/{0,1,2}/*)#4rqwxvej",
|
||||||
|
parser: btcTestnetParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/{0,1,2}/*)#4rqwxvej",
|
||||||
|
Xpub: "tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN",
|
||||||
|
Type: bchain.P2TR,
|
||||||
|
Bip: "86",
|
||||||
|
ChangeIndexes: []uint32{0, 1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tr([5c9e228d/86'/1'/0']tpubD/<0;1;2>/*)#4rqwxvej",
|
||||||
|
xpub: "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/<0;1;2>/*)#4rqwxvej",
|
||||||
|
parser: btcTestnetParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/<0;1;2>/*)#4rqwxvej",
|
||||||
|
Xpub: "tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN",
|
||||||
|
Type: bchain.P2TR,
|
||||||
|
Bip: "86",
|
||||||
|
ChangeIndexes: []uint32{0, 1, 2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tr([5c9e228d/86'/1'/0']tpubD/3/*)#4rqwxvej",
|
||||||
|
xpub: "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/3/*)#4rqwxvej",
|
||||||
|
parser: btcTestnetParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/3/*)#4rqwxvej",
|
||||||
|
Xpub: "tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN",
|
||||||
|
Type: bchain.P2TR,
|
||||||
|
Bip: "86",
|
||||||
|
ChangeIndexes: []uint32{3},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "xpub",
|
||||||
|
xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||||
|
Xpub: "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj",
|
||||||
|
Type: bchain.P2PKH,
|
||||||
|
Bip: "44",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "ypub",
|
||||||
|
xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||||
|
Xpub: "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP",
|
||||||
|
Type: bchain.P2SHWPKH,
|
||||||
|
Bip: "49",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "zpub",
|
||||||
|
xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||||
|
Xpub: "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs",
|
||||||
|
Type: bchain.P2WPKH,
|
||||||
|
Bip: "84",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sh(wpkh([5c9e228d/99'/0'/0']xpub/{122,123,4431}/*))",
|
||||||
|
xpub: "sh(wpkh([5c9e228d/99'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/{122,123,4431}/*))",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "sh(wpkh([5c9e228d/99'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/{122,123,4431}/*))",
|
||||||
|
Xpub: "xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ",
|
||||||
|
Type: bchain.P2SHWPKH,
|
||||||
|
Bip: "99",
|
||||||
|
ChangeIndexes: []uint32{122, 123, 4431},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sh(wpkh([5c9e228d/99'/0'/0']xpub/<122;123;4431>/*))",
|
||||||
|
xpub: "sh(wpkh([5c9e228d/99'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/<122;123;4431>/*))",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "sh(wpkh([5c9e228d/99'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/<122;123;4431>/*))",
|
||||||
|
Xpub: "xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ",
|
||||||
|
Type: bchain.P2SHWPKH,
|
||||||
|
Bip: "99",
|
||||||
|
ChangeIndexes: []uint32{122, 123, 4431},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "pkh(xpub)",
|
||||||
|
xpub: "pkh(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ)",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "pkh(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ)",
|
||||||
|
Xpub: "xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ",
|
||||||
|
Type: bchain.P2PKH,
|
||||||
|
Bip: "44",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sh(wpkh(xpub))",
|
||||||
|
xpub: "sh(wpkh(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ))",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "sh(wpkh(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ))",
|
||||||
|
Xpub: "xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ",
|
||||||
|
Type: bchain.P2SHWPKH,
|
||||||
|
Bip: "49",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "wpkh(xpub)",
|
||||||
|
xpub: "wpkh(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ)",
|
||||||
|
parser: btcMainParser,
|
||||||
|
want: &bchain.XpubDescriptor{
|
||||||
|
XpubDescriptor: "wpkh(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ)",
|
||||||
|
Xpub: "xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ",
|
||||||
|
Type: bchain.P2WPKH,
|
||||||
|
Bip: "84",
|
||||||
|
ChangeIndexes: []uint32{0, 1},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "xxx(xpub) error - unknown output script",
|
||||||
|
xpub: "xxx(xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ)",
|
||||||
|
parser: btcMainParser,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sh(wpkh([5c9e228d/99'/0'/0']xpub/{0,123,4431}/1)) error - * in index is mandatory",
|
||||||
|
xpub: "sh(wpkh([5c9e228d/99'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/{122,123,4431}/1))",
|
||||||
|
parser: btcMainParser,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sh(wpkh([5c9e228d/99'/0'/0']xpub/{0,123,4431}/1) error - path too long",
|
||||||
|
xpub: "sh(wpkh([5c9e228d/99'/0'/0']xpub6BgBgsespWvERF3LHQu6CnqdvfEvtMcQjYrcRzx53QJjSxarj2afYWcLteoGVky7D3UKDP9QyrLprQ3VCECoY49yfdDEHGCtMMj92pReUsQ/{122,123,4431}/1/*))",
|
||||||
|
parser: btcMainParser,
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
got, err := tt.parser.ParseXpub(tt.xpub)
|
||||||
|
if (err != nil) != tt.wantErr {
|
||||||
|
t.Errorf("ParseXpub() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
if got.ExtKey == nil {
|
||||||
|
t.Errorf("ParseXpub() got nil ExtKey")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got.ExtKey = nil
|
||||||
|
if !reflect.DeepEqual(got, tt.want) {
|
||||||
|
t.Errorf("ParseXpub() = %+v, want %+v", got, tt.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestDeriveAddressDescriptors(t *testing.T) {
|
func TestDeriveAddressDescriptors(t *testing.T) {
|
||||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
btcTestnetParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198})
|
btcTestnetParser := NewBitcoinParser(GetChainParams("test"), &Configuration{XPubMagic: 70617039, XPubMagicSegwitP2sh: 71979618, XPubMagicSegwitNative: 73342198})
|
||||||
@ -796,7 +1002,12 @@ func TestDeriveAddressDescriptors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.args.parser.DeriveAddressDescriptors(tt.args.xpub, tt.args.change, tt.args.indexes)
|
descriptor, err := tt.args.parser.ParseXpub(tt.args.xpub)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseXpub() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := tt.args.parser.DeriveAddressDescriptors(descriptor, tt.args.change, tt.args.indexes)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -891,7 +1102,12 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
descriptor, err := tt.args.parser.ParseXpub(tt.args.xpub)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseXpub() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(descriptor, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -915,21 +1131,24 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
|
|||||||
func BenchmarkDeriveAddressDescriptorsFromToXpub(b *testing.B) {
|
func BenchmarkDeriveAddressDescriptorsFromToXpub(b *testing.B) {
|
||||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
btcMainParser.DeriveAddressDescriptorsFromTo("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj", 1, 0, 100)
|
descriptor, _ := btcMainParser.ParseXpub("xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj")
|
||||||
|
btcMainParser.DeriveAddressDescriptorsFromTo(descriptor, 1, 0, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkDeriveAddressDescriptorsFromToYpub(b *testing.B) {
|
func BenchmarkDeriveAddressDescriptorsFromToYpub(b *testing.B) {
|
||||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
btcMainParser.DeriveAddressDescriptorsFromTo("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP", 1, 0, 100)
|
descriptor, _ := btcMainParser.ParseXpub("ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP")
|
||||||
|
btcMainParser.DeriveAddressDescriptorsFromTo(descriptor, 1, 0, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkDeriveAddressDescriptorsFromToZpub(b *testing.B) {
|
func BenchmarkDeriveAddressDescriptorsFromToZpub(b *testing.B) {
|
||||||
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
btcMainParser := NewBitcoinParser(GetChainParams("main"), &Configuration{XPubMagic: 76067358, XPubMagicSegwitP2sh: 77429938, XPubMagicSegwitNative: 78792518})
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
btcMainParser.DeriveAddressDescriptorsFromTo("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", 1, 0, 100)
|
descriptor, _ := btcMainParser.ParseXpub("zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs")
|
||||||
|
btcMainParser.DeriveAddressDescriptorsFromTo(descriptor, 1, 0, 100)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1006,7 +1225,12 @@ func TestBitcoinParser_DerivationBasePath(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.args.parser.DerivationBasePath(tt.args.xpub)
|
descriptor, err := tt.args.parser.ParseXpub(tt.args.xpub)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseXpub() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := tt.args.parser.DerivationBasePath(descriptor)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("BitcoinParser.DerivationBasePath() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("BitcoinParser.DerivationBasePath() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -254,16 +254,28 @@ func (p *DecredParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey) (bchai
|
|||||||
return p.GetAddrDescFromAddress(addr.String())
|
return p.GetAddrDescFromAddress(addr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeriveAddressDescriptors derives address descriptors from given xpub for
|
// ParseXpub parses xpub (or xpub descriptor) and returns XpubDescriptor
|
||||||
// listed indexes
|
func (p *DecredParser) ParseXpub(xpub string) (*bchain.XpubDescriptor, error) {
|
||||||
func (p *DecredParser) DeriveAddressDescriptors(xpub string, change uint32,
|
var descriptor bchain.XpubDescriptor
|
||||||
indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
|
||||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.netConfig)
|
extKey, err := hdkeychain.NewKeyFromString(xpub, p.netConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
descriptor.Xpub = xpub
|
||||||
|
descriptor.XpubDescriptor = xpub
|
||||||
|
descriptor.Type = bchain.P2PKH
|
||||||
|
descriptor.Bip = "44"
|
||||||
|
descriptor.ChangeIndexes = []uint32{0, 1}
|
||||||
|
descriptor.ExtKey = extKey
|
||||||
|
return &descriptor, nil
|
||||||
|
}
|
||||||
|
|
||||||
changeExtKey, err := extKey.Child(change)
|
// DeriveAddressDescriptors derives address descriptors from given xpub for
|
||||||
|
// listed indexes
|
||||||
|
func (p *DecredParser) DeriveAddressDescriptors(descriptor *bchain.XpubDescriptor, change uint32,
|
||||||
|
indexes []uint32) ([]bchain.AddressDescriptor, error) {
|
||||||
|
|
||||||
|
changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Child(change)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -284,16 +296,13 @@ func (p *DecredParser) DeriveAddressDescriptors(xpub string, change uint32,
|
|||||||
|
|
||||||
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for
|
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for
|
||||||
// addresses in index range
|
// addresses in index range
|
||||||
func (p *DecredParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32,
|
func (p *DecredParser) DeriveAddressDescriptorsFromTo(descriptor *bchain.XpubDescriptor, change uint32,
|
||||||
fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
||||||
if toIndex <= fromIndex {
|
if toIndex <= fromIndex {
|
||||||
return nil, errors.New("toIndex<=fromIndex")
|
return nil, errors.New("toIndex<=fromIndex")
|
||||||
}
|
}
|
||||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.netConfig)
|
|
||||||
if err != nil {
|
changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Child(change)
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
changeExtKey, err := extKey.Child(change)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -316,9 +325,9 @@ func (p *DecredParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32
|
|||||||
// m/44'/<coin type>'/<account>'/<branch>/<address index>. This function only
|
// m/44'/<coin type>'/<account>'/<branch>/<address index>. This function only
|
||||||
// returns a path up to m/44'/<coin type>'/<account>'/ whereby the rest of the
|
// returns a path up to m/44'/<coin type>'/<account>'/ whereby the rest of the
|
||||||
// other details (<branch>/<address index>) are populated automatically.
|
// other details (<branch>/<address index>) are populated automatically.
|
||||||
func (p *DecredParser) DerivationBasePath(xpub string) (string, error) {
|
func (p *DecredParser) DerivationBasePath(descriptor *bchain.XpubDescriptor) (string, error) {
|
||||||
var c string
|
var c string
|
||||||
cn, depth, err := p.decodeXpub(xpub)
|
cn, depth, err := p.decodeXpub(descriptor.Xpub)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
//go:build unittest
|
||||||
// +build unittest
|
// +build unittest
|
||||||
|
|
||||||
package dcr
|
package dcr
|
||||||
@ -336,7 +337,12 @@ func TestDeriveAddressDescriptors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.args.parser.DeriveAddressDescriptors(tt.args.xpub, tt.args.change, tt.args.indexes)
|
descriptor, err := tt.args.parser.ParseXpub(tt.args.xpub)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseXpub() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := tt.args.parser.DeriveAddressDescriptors(descriptor, tt.args.change, tt.args.indexes)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -419,7 +425,12 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
|
|||||||
}
|
}
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
descriptor, err := tt.args.parser.ParseXpub(tt.args.xpub)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseXpub() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := tt.args.parser.DeriveAddressDescriptorsFromTo(descriptor, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
@ -470,7 +481,12 @@ func TestDerivationBasePath(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := tt.parser.DerivationBasePath(tt.xpub)
|
descriptor, err := tt.parser.ParseXpub(tt.xpub)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseXpub() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := tt.parser.DerivationBasePath(descriptor)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("DerivationBasePath() expected no error but got %v", err)
|
t.Errorf("DerivationBasePath() expected no error but got %v", err)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -161,15 +161,11 @@ func (p *NulsParser) ParseTx(b []byte) (*bchain.Tx, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
|
// DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
|
||||||
func (p *NulsParser) DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
func (p *NulsParser) DeriveAddressDescriptorsFromTo(descriptor *bchain.XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
|
||||||
if toIndex <= fromIndex {
|
if toIndex <= fromIndex {
|
||||||
return nil, errors.New("toIndex<=fromIndex")
|
return nil, errors.New("toIndex<=fromIndex")
|
||||||
}
|
}
|
||||||
extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
|
changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Derive(change)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
changeExtKey, err := extKey.Derive(change)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -486,7 +486,12 @@ func TestDeriveAddressDescriptorsFromTo(t *testing.T) {
|
|||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := parser.DeriveAddressDescriptorsFromTo(tt.args.xpub, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
descriptor, err := parser.ParseXpub(tt.args.xpub)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("ParseXpub() error = %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
got, err := parser.DeriveAddressDescriptorsFromTo(descriptor, tt.args.change, tt.args.fromIndex, tt.args.toIndex)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("DeriveAddressDescriptorsFromTo() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -197,7 +197,7 @@ func AddressDescriptorFromString(s string) (AddressDescriptor, error) {
|
|||||||
if len(s) > 3 && s[0:3] == "ad:" {
|
if len(s) > 3 && s[0:3] == "ad:" {
|
||||||
return hex.DecodeString(s[3:])
|
return hex.DecodeString(s[3:])
|
||||||
}
|
}
|
||||||
return nil, errors.New("Not AddressDescriptor")
|
return nil, errors.New("invalid address descriptor")
|
||||||
}
|
}
|
||||||
|
|
||||||
// EthereumType specific
|
// EthereumType specific
|
||||||
@ -224,6 +224,28 @@ type MempoolTxidEntry struct {
|
|||||||
Time uint32
|
Time uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ScriptType - type of output script parsed from xpub (descriptor)
|
||||||
|
type ScriptType int
|
||||||
|
|
||||||
|
// ScriptType enumeration
|
||||||
|
const (
|
||||||
|
P2PK = ScriptType(iota)
|
||||||
|
P2PKH
|
||||||
|
P2SHWPKH
|
||||||
|
P2WPKH
|
||||||
|
P2TR
|
||||||
|
)
|
||||||
|
|
||||||
|
// XpubDescriptor contains parsed data from xpub descriptor
|
||||||
|
type XpubDescriptor struct {
|
||||||
|
XpubDescriptor string // The whole descriptor
|
||||||
|
Xpub string // Xpub part of the descriptor
|
||||||
|
Type ScriptType
|
||||||
|
Bip string
|
||||||
|
ChangeIndexes []uint32
|
||||||
|
ExtKey interface{} // extended key parsed from xpub, usually of type *hdkeychain.ExtendedKey
|
||||||
|
}
|
||||||
|
|
||||||
// MempoolTxidEntries is array of MempoolTxidEntry
|
// MempoolTxidEntries is array of MempoolTxidEntry
|
||||||
type MempoolTxidEntries []MempoolTxidEntry
|
type MempoolTxidEntries []MempoolTxidEntry
|
||||||
|
|
||||||
@ -317,9 +339,10 @@ type BlockChainParser interface {
|
|||||||
UnpackBlockHash(buf []byte) (string, error)
|
UnpackBlockHash(buf []byte) (string, error)
|
||||||
ParseBlock(b []byte) (*Block, error)
|
ParseBlock(b []byte) (*Block, error)
|
||||||
// xpub
|
// xpub
|
||||||
DerivationBasePath(xpub string) (string, error)
|
ParseXpub(xpub string) (*XpubDescriptor, error)
|
||||||
DeriveAddressDescriptors(xpub string, change uint32, indexes []uint32) ([]AddressDescriptor, error)
|
DerivationBasePath(descriptor *XpubDescriptor) (string, error)
|
||||||
DeriveAddressDescriptorsFromTo(xpub string, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error)
|
DeriveAddressDescriptors(descriptor *XpubDescriptor, change uint32, indexes []uint32) ([]AddressDescriptor, error)
|
||||||
|
DeriveAddressDescriptorsFromTo(descriptor *XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]AddressDescriptor, error)
|
||||||
// EthereumType specific
|
// EthereumType specific
|
||||||
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
|
EthereumTypeGetErc20FromTx(tx *Tx) ([]Erc20Transfer, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -413,7 +413,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
|||||||
contentType: "text/html; charset=utf-8",
|
contentType: "text/html; charset=utf-8",
|
||||||
body: []string{
|
body: []string{
|
||||||
`<a class="navbar-brand" href="/">Fake Coin Explorer</a>`,
|
`<a class="navbar-brand" href="/">Fake Coin Explorer</a>`,
|
||||||
`<h1>XPUB <small class="text-muted">0 FAKE</small></h1><div class="alert alert-data ellipsis"><span class="data">tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/0/*)#4rqwxvej</span></div><h3>Confirmed</h3>`,
|
`<h1>XPUB <small class="text-muted">0 FAKE</small></h1><div class="alert alert-data ellipsis"><span class="data">tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/{0,1}/*)#4rqwxvej</span></div><h3>Confirmed</h3>`,
|
||||||
`<tr><td style="width: 25%;">Total Received</td><td class="data">0 FAKE</td></tr>`,
|
`<tr><td style="width: 25%;">Total Received</td><td class="data">0 FAKE</td></tr>`,
|
||||||
`<tr><td>Total Sent</td><td class="data">0 FAKE</td></tr>`,
|
`<tr><td>Total Sent</td><td class="data">0 FAKE</td></tr>`,
|
||||||
`<tr><td>Used XPUB Addresses</td><td class="data">0</td></tr>`,
|
`<tr><td>Used XPUB Addresses</td><td class="data">0</td></tr>`,
|
||||||
@ -719,7 +719,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
|||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
body: []string{
|
body: []string{
|
||||||
`{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/0/*)#4rqwxvej","balance":"0","totalReceived":"0","totalSent":"0","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":0,"tokens":[{"type":"XPUBAddress","name":"tb1pswrqtykue8r89t9u4rprjs0gt4qzkdfuursfnvqaa3f2yql07zmq8s8a5u","path":"m/86'/1'/0'/0/0","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p8tvmvsvhsee73rhym86wt435qrqm92psfsyhy6a3n5gw455znnpqm8wald","path":"m/86'/1'/0'/0/1","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p537ddhyuydg5c2v75xxmn6ac64yz4xns2x0gpdcwj5vzzzgrywlqlqwk43","path":"m/86'/1'/0'/0/2","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1pn2d0yjeedavnkd8z8lhm566p0f2utm3lgvxrsdehnl94y34txmts5s7t4c","path":"m/86'/1'/0'/1/0","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p0pnd6ue5vryymvd28aeq3kdz6rmsdjqrq6eespgtg8wdgnxjzjksujhq4u","path":"m/86'/1'/0'/1/1","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p29gpmd96hhgf7wj2vs03ca7x2xx39g8t6e0p55h2d5ssqs4fsj8qtx00wc","path":"m/86'/1'/0'/1/2","transfers":0,"decimals":8}]}`,
|
`{"page":1,"totalPages":1,"itemsOnPage":1000,"address":"tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/{0,1}/*)#4rqwxvej","balance":"0","totalReceived":"0","totalSent":"0","unconfirmedBalance":"0","unconfirmedTxs":0,"txs":0,"tokens":[{"type":"XPUBAddress","name":"tb1pswrqtykue8r89t9u4rprjs0gt4qzkdfuursfnvqaa3f2yql07zmq8s8a5u","path":"m/86'/1'/0'/0/0","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p8tvmvsvhsee73rhym86wt435qrqm92psfsyhy6a3n5gw455znnpqm8wald","path":"m/86'/1'/0'/0/1","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p537ddhyuydg5c2v75xxmn6ac64yz4xns2x0gpdcwj5vzzzgrywlqlqwk43","path":"m/86'/1'/0'/0/2","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1pn2d0yjeedavnkd8z8lhm566p0f2utm3lgvxrsdehnl94y34txmts5s7t4c","path":"m/86'/1'/0'/1/0","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p0pnd6ue5vryymvd28aeq3kdz6rmsdjqrq6eespgtg8wdgnxjzjksujhq4u","path":"m/86'/1'/0'/1/1","transfers":0,"decimals":8},{"type":"XPUBAddress","name":"tb1p29gpmd96hhgf7wj2vs03ca7x2xx39g8t6e0p55h2d5ssqs4fsj8qtx00wc","path":"m/86'/1'/0'/1/2","transfers":0,"decimals":8}]}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@ -18,7 +18,7 @@ const (
|
|||||||
TxidB2T4 = "fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"
|
TxidB2T4 = "fdd824a780cbb718eeb766eb05d83fdefc793a27082cd5e67f856d69798cf7db"
|
||||||
|
|
||||||
Xpub = "upub5E1xjDmZ7Hhej6LPpS8duATdKXnRYui7bDYj6ehfFGzWDZtmCmQkZhc3Zb7kgRLtHWd16QFxyP86JKL3ShZEBFX88aciJ3xyocuyhZZ8g6q"
|
Xpub = "upub5E1xjDmZ7Hhej6LPpS8duATdKXnRYui7bDYj6ehfFGzWDZtmCmQkZhc3Zb7kgRLtHWd16QFxyP86JKL3ShZEBFX88aciJ3xyocuyhZZ8g6q"
|
||||||
TaprootDescriptor = "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/0/*)#4rqwxvej"
|
TaprootDescriptor = "tr([5c9e228d/86'/1'/0']tpubDC88gkaZi5HvJGxGDNLADkvtdpni3mLmx6vr2KnXmWMG8zfkBRggsxHVBkUpgcwPe2KKpkyvTJCdXHb1UHEWE64vczyyPQfHr1skBcsRedN/{0,1}/*)#4rqwxvej"
|
||||||
|
|
||||||
Addr1 = "mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti" // 76a914010d39800f86122416e28f485029acf77507169288ac
|
Addr1 = "mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti" // 76a914010d39800f86122416e28f485029acf77507169288ac
|
||||||
Addr2 = "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz" // 76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac
|
Addr2 = "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz" // 76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user