github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/btc/bitcoinlikeparser.go (about)

     1  package btc
     2  
     3  import (
     4  	"bytes"
     5  	"crypto/sha256"
     6  	"encoding/binary"
     7  	"encoding/hex"
     8  	"math/big"
     9  	"regexp"
    10  	"strconv"
    11  	"strings"
    12  	"unicode/utf8"
    13  
    14  	vlq "github.com/bsm/go-vlq"
    15  	"github.com/juju/errors"
    16  	"github.com/martinboehm/btcd/blockchain"
    17  	"github.com/martinboehm/btcd/btcec"
    18  	"github.com/martinboehm/btcd/wire"
    19  	"github.com/martinboehm/btcutil"
    20  	"github.com/martinboehm/btcutil/chaincfg"
    21  	"github.com/martinboehm/btcutil/hdkeychain"
    22  	"github.com/martinboehm/btcutil/txscript"
    23  	"github.com/trezor/blockbook/bchain"
    24  )
    25  
    26  // OutputScriptToAddressesFunc converts ScriptPubKey to bitcoin addresses
    27  type OutputScriptToAddressesFunc func(script []byte) ([]string, bool, error)
    28  
    29  // BitcoinLikeParser handle
    30  type BitcoinLikeParser struct {
    31  	*bchain.BaseParser
    32  	Params                       *chaincfg.Params
    33  	OutputScriptToAddressesFunc  OutputScriptToAddressesFunc
    34  	XPubMagic                    uint32
    35  	XPubMagicSegwitP2sh          uint32
    36  	XPubMagicSegwitNative        uint32
    37  	Slip44                       uint32
    38  	VSizeSupport                 bool
    39  	minimumCoinbaseConfirmations int
    40  }
    41  
    42  // NewBitcoinLikeParser returns new BitcoinLikeParser instance
    43  func NewBitcoinLikeParser(params *chaincfg.Params, c *Configuration) *BitcoinLikeParser {
    44  	p := &BitcoinLikeParser{
    45  		BaseParser: &bchain.BaseParser{
    46  			BlockAddressesToKeep: c.BlockAddressesToKeep,
    47  			AmountDecimalPoint:   8,
    48  			AddressAliases:       c.AddressAliases,
    49  		},
    50  		Params:                       params,
    51  		XPubMagic:                    c.XPubMagic,
    52  		XPubMagicSegwitP2sh:          c.XPubMagicSegwitP2sh,
    53  		XPubMagicSegwitNative:        c.XPubMagicSegwitNative,
    54  		Slip44:                       c.Slip44,
    55  		minimumCoinbaseConfirmations: c.MinimumCoinbaseConfirmations,
    56  	}
    57  	p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
    58  	return p
    59  }
    60  
    61  // GetAddrDescFromVout returns internal address representation (descriptor) of given transaction output
    62  func (p *BitcoinLikeParser) GetAddrDescFromVout(output *bchain.Vout) (bchain.AddressDescriptor, error) {
    63  	ad, err := hex.DecodeString(output.ScriptPubKey.Hex)
    64  	if err != nil {
    65  		return ad, err
    66  	}
    67  	// convert possible P2PK script to P2PKH
    68  	// so that all transactions by given public key are indexed together
    69  	return txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, ad)
    70  }
    71  
    72  // GetAddrDescFromAddress returns internal address representation (descriptor) of given address
    73  func (p *BitcoinLikeParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) {
    74  	return p.addressToOutputScript(address)
    75  }
    76  
    77  // GetAddressesFromAddrDesc returns addresses for given address descriptor with flag if the addresses are searchable
    78  func (p *BitcoinLikeParser) GetAddressesFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]string, bool, error) {
    79  	return p.OutputScriptToAddressesFunc(addrDesc)
    80  }
    81  
    82  // GetScriptFromAddrDesc returns output script for given address descriptor
    83  func (p *BitcoinLikeParser) GetScriptFromAddrDesc(addrDesc bchain.AddressDescriptor) ([]byte, error) {
    84  	return addrDesc, nil
    85  }
    86  
    87  // IsAddrDescIndexable returns true if AddressDescriptor should be added to index
    88  // empty or OP_RETURN scripts are not indexed
    89  func (p *BitcoinLikeParser) IsAddrDescIndexable(addrDesc bchain.AddressDescriptor) bool {
    90  	if len(addrDesc) == 0 || addrDesc[0] == txscript.OP_RETURN {
    91  		return false
    92  	}
    93  	return true
    94  }
    95  
    96  // addressToOutputScript converts bitcoin address to ScriptPubKey
    97  func (p *BitcoinLikeParser) addressToOutputScript(address string) ([]byte, error) {
    98  	da, err := btcutil.DecodeAddress(address, p.Params)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	script, err := txscript.PayToAddrScript(da)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	return script, nil
   107  }
   108  
   109  // TryParseOPReturn tries to process OP_RETURN script and return its string representation
   110  func (p *BitcoinLikeParser) TryParseOPReturn(script []byte) string {
   111  	if len(script) > 1 && script[0] == txscript.OP_RETURN {
   112  		// trying 2 variants of OP_RETURN data
   113  		// 1) OP_RETURN OP_PUSHDATA1 <datalen> <data>
   114  		// 2) OP_RETURN <datalen> <data>
   115  		// 3) OP_RETURN OP_PUSHDATA2 <datalenlow> <datalenhigh> <data>
   116  		var data []byte
   117  		var l int
   118  		if script[1] == txscript.OP_PUSHDATA1 && len(script) > 2 {
   119  			l = int(script[2])
   120  			data = script[3:]
   121  			if l != len(data) {
   122  				l = int(script[1])
   123  				data = script[2:]
   124  			}
   125  		} else if script[1] == txscript.OP_PUSHDATA2 && len(script) > 3 {
   126  			l = int(script[2]) + int(script[3])<<8
   127  			data = script[4:]
   128  		} else {
   129  			l = int(script[1])
   130  			data = script[2:]
   131  		}
   132  		if l == len(data) {
   133  			var ed string
   134  
   135  			ed = p.tryParseOmni(data)
   136  			if ed != "" {
   137  				return ed
   138  			}
   139  
   140  			if utf8.Valid(data) {
   141  				ed = "(" + string(data) + ")"
   142  			} else {
   143  				ed = hex.EncodeToString(data)
   144  			}
   145  			return "OP_RETURN " + ed
   146  		}
   147  	}
   148  	return ""
   149  }
   150  
   151  var omniCurrencyMap = map[uint32]string{
   152  	1:  "Omni",
   153  	2:  "Test Omni",
   154  	31: "TetherUS",
   155  }
   156  
   157  // tryParseOmni tries to extract Omni simple send transaction from script
   158  func (p *BitcoinLikeParser) tryParseOmni(data []byte) string {
   159  
   160  	// currently only simple send transaction version 0 is supported, see
   161  	// https://github.com/OmniLayer/spec#transfer-coins-simple-send
   162  	if len(data) != 20 || data[0] != 'o' {
   163  		return ""
   164  	}
   165  	// omni (4) <tx_version> (2) <tx_type> (2)
   166  	omniHeader := []byte{'o', 'm', 'n', 'i', 0, 0, 0, 0}
   167  	if !bytes.Equal(data[0:8], omniHeader) {
   168  		return ""
   169  	}
   170  
   171  	currencyID := binary.BigEndian.Uint32(data[8:12])
   172  	currency, ok := omniCurrencyMap[currencyID]
   173  	if !ok {
   174  		return ""
   175  	}
   176  	amount := new(big.Int)
   177  	amount.SetBytes(data[12:])
   178  	amountStr := p.AmountToDecimalString(amount)
   179  
   180  	ed := "OMNI Simple Send: " + amountStr + " " + currency + " (#" + strconv.Itoa(int(currencyID)) + ")"
   181  	return ed
   182  }
   183  
   184  // outputScriptToAddresses converts ScriptPubKey to addresses with a flag that the addresses are searchable
   185  func (p *BitcoinLikeParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
   186  	sc, addresses, _, err := txscript.ExtractPkScriptAddrs(script, p.Params)
   187  	if err != nil {
   188  		return nil, false, err
   189  	}
   190  	rv := make([]string, len(addresses))
   191  	for i, a := range addresses {
   192  		rv[i] = a.EncodeAddress()
   193  	}
   194  	var s bool
   195  	if sc == txscript.PubKeyHashTy || sc == txscript.WitnessV0PubKeyHashTy || sc == txscript.ScriptHashTy || sc == txscript.WitnessV0ScriptHashTy || sc == txscript.WitnessV1TaprootTy {
   196  		s = true
   197  	} else if len(rv) == 0 {
   198  		or := p.TryParseOPReturn(script)
   199  		if or != "" {
   200  			rv = []string{or}
   201  		}
   202  	}
   203  	return rv, s, nil
   204  }
   205  
   206  // TxFromMsgTx converts bitcoin wire Tx to bchain.Tx
   207  func (p *BitcoinLikeParser) TxFromMsgTx(t *wire.MsgTx, parseAddresses bool) bchain.Tx {
   208  	var vSize int64
   209  	if p.VSizeSupport {
   210  		baseSize := t.SerializeSizeStripped()
   211  		totalSize := t.SerializeSize()
   212  		weight := int64((baseSize * (blockchain.WitnessScaleFactor - 1)) + totalSize)
   213  		vSize = (weight + (blockchain.WitnessScaleFactor - 1)) / blockchain.WitnessScaleFactor
   214  	}
   215  
   216  	vin := make([]bchain.Vin, len(t.TxIn))
   217  	for i, in := range t.TxIn {
   218  		if blockchain.IsCoinBaseTx(t) {
   219  			vin[i] = bchain.Vin{
   220  				Coinbase: hex.EncodeToString(in.SignatureScript),
   221  				Sequence: in.Sequence,
   222  			}
   223  			break
   224  		}
   225  		s := bchain.ScriptSig{
   226  			Hex: hex.EncodeToString(in.SignatureScript),
   227  			// missing: Asm,
   228  		}
   229  		vin[i] = bchain.Vin{
   230  			Txid:      in.PreviousOutPoint.Hash.String(),
   231  			Vout:      in.PreviousOutPoint.Index,
   232  			Sequence:  in.Sequence,
   233  			ScriptSig: s,
   234  			Witness:   in.Witness,
   235  		}
   236  	}
   237  	vout := make([]bchain.Vout, len(t.TxOut))
   238  	for i, out := range t.TxOut {
   239  		addrs := []string{}
   240  		if parseAddresses {
   241  			addrs, _, _ = p.OutputScriptToAddressesFunc(out.PkScript)
   242  		}
   243  		s := bchain.ScriptPubKey{
   244  			Hex:       hex.EncodeToString(out.PkScript),
   245  			Addresses: addrs,
   246  			// missing: Asm,
   247  			// missing: Type,
   248  		}
   249  		var vs big.Int
   250  		vs.SetInt64(out.Value)
   251  		vout[i] = bchain.Vout{
   252  			ValueSat:     vs,
   253  			N:            uint32(i),
   254  			ScriptPubKey: s,
   255  		}
   256  	}
   257  	tx := bchain.Tx{
   258  		Txid:     t.TxHash().String(),
   259  		Version:  t.Version,
   260  		LockTime: t.LockTime,
   261  		VSize:    vSize,
   262  		Vin:      vin,
   263  		Vout:     vout,
   264  		// skip: BlockHash,
   265  		// skip: Confirmations,
   266  		// skip: Time,
   267  		// skip: Blocktime,
   268  	}
   269  	return tx
   270  }
   271  
   272  // ParseTx parses byte array containing transaction and returns Tx struct
   273  func (p *BitcoinLikeParser) ParseTx(b []byte) (*bchain.Tx, error) {
   274  	t := wire.MsgTx{}
   275  	r := bytes.NewReader(b)
   276  	if err := t.Deserialize(r); err != nil {
   277  		return nil, err
   278  	}
   279  	tx := p.TxFromMsgTx(&t, true)
   280  	tx.Hex = hex.EncodeToString(b)
   281  	return &tx, nil
   282  }
   283  
   284  // ParseBlock parses raw block to our Block struct
   285  func (p *BitcoinLikeParser) ParseBlock(b []byte) (*bchain.Block, error) {
   286  	w := wire.MsgBlock{}
   287  	r := bytes.NewReader(b)
   288  
   289  	if err := w.Deserialize(r); err != nil {
   290  		return nil, err
   291  	}
   292  
   293  	txs := make([]bchain.Tx, len(w.Transactions))
   294  	for ti, t := range w.Transactions {
   295  		txs[ti] = p.TxFromMsgTx(t, false)
   296  	}
   297  
   298  	return &bchain.Block{
   299  		BlockHeader: bchain.BlockHeader{
   300  			Size: len(b),
   301  			Time: w.Header.Timestamp.Unix(),
   302  		},
   303  		Txs: txs,
   304  	}, nil
   305  }
   306  
   307  // PackTx packs transaction to byte array
   308  func (p *BitcoinLikeParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
   309  	buf := make([]byte, 4+vlq.MaxLen64+len(tx.Hex)/2)
   310  	binary.BigEndian.PutUint32(buf[0:4], height)
   311  	vl := vlq.PutInt(buf[4:4+vlq.MaxLen64], blockTime)
   312  	hl, err := hex.Decode(buf[4+vl:], []byte(tx.Hex))
   313  	return buf[0 : 4+vl+hl], err
   314  }
   315  
   316  // UnpackTx unpacks transaction from byte array
   317  func (p *BitcoinLikeParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
   318  	height := binary.BigEndian.Uint32(buf)
   319  	bt, l := vlq.Int(buf[4:])
   320  	tx, err := p.ParseTx(buf[4+l:])
   321  	if err != nil {
   322  		return nil, 0, err
   323  	}
   324  	tx.Blocktime = bt
   325  
   326  	return tx, height, nil
   327  }
   328  
   329  // MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
   330  func (p *BitcoinLikeParser) MinimumCoinbaseConfirmations() int {
   331  	return p.minimumCoinbaseConfirmations
   332  }
   333  
   334  // SupportsVSize returns true if vsize of a transaction should be computed and returned by API
   335  func (p *BitcoinLikeParser) SupportsVSize() bool {
   336  	return p.VSizeSupport
   337  }
   338  
   339  var tapTweakTagHash = sha256.Sum256([]byte("TapTweak"))
   340  
   341  func tapTweakHash(msg []byte) []byte {
   342  	tagLen := len(tapTweakTagHash)
   343  	m := make([]byte, tagLen*2+len(msg))
   344  	copy(m[:tagLen], tapTweakTagHash[:])
   345  	copy(m[tagLen:tagLen*2], tapTweakTagHash[:])
   346  	copy(m[tagLen*2:], msg)
   347  	h := sha256.Sum256(m)
   348  	return h[:]
   349  }
   350  
   351  func (p *BitcoinLikeParser) taprootAddrFromExtKey(extKey *hdkeychain.ExtendedKey) (*btcutil.AddressWitnessTaproot, error) {
   352  	curve := btcec.S256()
   353  	t := new(big.Int)
   354  
   355  	// tweak the derived pubkey to the output pub key according to https://en.bitcoin.it/wiki/BIP_0341
   356  	// and https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
   357  	derived_key := extKey.PubKeyBytes()[1:]
   358  
   359  	t.SetBytes(tapTweakHash(derived_key))
   360  	// Fail if t >=order of the base point
   361  	if t.Cmp(curve.N) >= 0 {
   362  		return nil, errors.New("greater than or equal to curve order")
   363  	}
   364  	// Q = point_add(lift_x(int_from_bytes(pubkey)), point_mul(G, t))
   365  	ipx, ipy, err := btcec.LiftX(derived_key)
   366  	if err != nil {
   367  		return nil, err
   368  	}
   369  	tGx, tGy := curve.ScalarBaseMult(t.Bytes())
   370  	output_pubkey, _ := curve.Add(ipx, ipy, tGx, tGy)
   371  	//
   372  	b := output_pubkey.Bytes()
   373  	// the x coordinate on the curve can be a number small enough that it does not need 32 bytes required for the output script
   374  	if len(b) < 32 {
   375  		b = make([]byte, 32)
   376  		output_pubkey.FillBytes(b)
   377  	}
   378  	return btcutil.NewAddressWitnessTaproot(b, p.Params)
   379  }
   380  
   381  func (p *BitcoinLikeParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey, descriptor *bchain.XpubDescriptor) (bchain.AddressDescriptor, error) {
   382  	var a btcutil.Address
   383  	var err error
   384  	switch descriptor.Type {
   385  	case bchain.P2PKH:
   386  		a, err = extKey.Address(p.Params)
   387  	case bchain.P2SHWPKH:
   388  		// redeemScript <witness version: OP_0><len pubKeyHash: 20><20-byte-pubKeyHash>
   389  		pubKeyHash := btcutil.Hash160(extKey.PubKeyBytes())
   390  		redeemScript := make([]byte, len(pubKeyHash)+2)
   391  		redeemScript[0] = 0
   392  		redeemScript[1] = byte(len(pubKeyHash))
   393  		copy(redeemScript[2:], pubKeyHash)
   394  		hash := btcutil.Hash160(redeemScript)
   395  		a, err = btcutil.NewAddressScriptHashFromHash(hash, p.Params)
   396  	case bchain.P2WPKH:
   397  		a, err = btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(extKey.PubKeyBytes()), p.Params)
   398  	case bchain.P2TR:
   399  		a, err = p.taprootAddrFromExtKey(extKey)
   400  	default:
   401  		return nil, errors.New("Unsupported xpub descriptor type")
   402  	}
   403  	if err != nil {
   404  		return nil, err
   405  	}
   406  	return txscript.PayToAddrScript(a)
   407  }
   408  
   409  func (p *BitcoinLikeParser) xpubDescriptorFromXpub(xpub string) (*bchain.XpubDescriptor, error) {
   410  	var descriptor bchain.XpubDescriptor
   411  	extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
   412  	if err != nil {
   413  		return nil, err
   414  	}
   415  	descriptor.Xpub = xpub
   416  	descriptor.XpubDescriptor = xpub
   417  	if extKey.Version() == p.XPubMagicSegwitP2sh {
   418  		descriptor.Type = bchain.P2SHWPKH
   419  		descriptor.Bip = "49"
   420  	} else if extKey.Version() == p.XPubMagicSegwitNative {
   421  		descriptor.Type = bchain.P2WPKH
   422  		descriptor.Bip = "84"
   423  	} else {
   424  		descriptor.Type = bchain.P2PKH
   425  		descriptor.Bip = "44"
   426  	}
   427  	descriptor.ChangeIndexes = []uint32{0, 1}
   428  	descriptor.ExtKey = extKey
   429  	return &descriptor, nil
   430  }
   431  
   432  var (
   433  	xpubDesriptorRegex     *regexp.Regexp
   434  	typeSubexpIndex        int
   435  	bipSubexpIndex         int
   436  	xpubSubexpIndex        int
   437  	changeSubexpIndex      int
   438  	changeList1SubexpIndex int
   439  	changeList2SubexpIndex int
   440  )
   441  
   442  func init() {
   443  	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+))/\*)?\)+`)
   444  	typeSubexpIndex = xpubDesriptorRegex.SubexpIndex("type")
   445  	bipSubexpIndex = xpubDesriptorRegex.SubexpIndex("bip")
   446  	xpubSubexpIndex = xpubDesriptorRegex.SubexpIndex("xpub")
   447  	changeList1SubexpIndex = xpubDesriptorRegex.SubexpIndex("changelist1")
   448  	changeList2SubexpIndex = xpubDesriptorRegex.SubexpIndex("changelist2")
   449  	changeSubexpIndex = xpubDesriptorRegex.SubexpIndex("change")
   450  	if changeSubexpIndex < 0 {
   451  		panic("Invalid bitcoinparser xpubDesriptorRegex")
   452  	}
   453  }
   454  
   455  // ParseXpub parses xpub (or xpub descriptor) and returns XpubDescriptor
   456  func (p *BitcoinLikeParser) ParseXpub(xpub string) (*bchain.XpubDescriptor, error) {
   457  	match := xpubDesriptorRegex.FindStringSubmatch(xpub)
   458  	if len(match) > changeSubexpIndex {
   459  		var descriptor bchain.XpubDescriptor
   460  		descriptor.XpubDescriptor = xpub
   461  		m := match[typeSubexpIndex]
   462  		switch m {
   463  		case "pkh":
   464  			descriptor.Type = bchain.P2PKH
   465  			descriptor.Bip = "44"
   466  		case "sh(wpkh":
   467  			descriptor.Type = bchain.P2SHWPKH
   468  			descriptor.Bip = "49"
   469  		case "wpkh":
   470  			descriptor.Type = bchain.P2WPKH
   471  			descriptor.Bip = "84"
   472  		case "tr":
   473  			descriptor.Type = bchain.P2TR
   474  			descriptor.Bip = "86"
   475  		default:
   476  			return nil, errors.Errorf("Xpub descriptor %s is not supported", m)
   477  		}
   478  		if len(match[bipSubexpIndex]) > 0 {
   479  			descriptor.Bip = match[bipSubexpIndex]
   480  		}
   481  		descriptor.Xpub = match[xpubSubexpIndex]
   482  		extKey, err := hdkeychain.NewKeyFromString(descriptor.Xpub, p.Params.Base58CksumHasher)
   483  		if err != nil {
   484  			return nil, err
   485  		}
   486  		descriptor.ExtKey = extKey
   487  		if len(match[changeSubexpIndex]) > 0 {
   488  			change, err := strconv.ParseUint(match[changeSubexpIndex], 10, 32)
   489  			if err != nil {
   490  				return nil, err
   491  			}
   492  			descriptor.ChangeIndexes = []uint32{uint32(change)}
   493  		} else {
   494  			if len(match[changeList1SubexpIndex]) > 0 || len(match[changeList2SubexpIndex]) > 0 {
   495  				var changes []string
   496  				if len(match[changeList1SubexpIndex]) > 0 {
   497  					changes = strings.Split(match[changeList1SubexpIndex], ",")
   498  				} else {
   499  					changes = strings.Split(match[changeList2SubexpIndex], ";")
   500  				}
   501  				if len(changes) == 0 {
   502  					return nil, errors.New("Invalid xpub descriptor, cannot parse change")
   503  				}
   504  				descriptor.ChangeIndexes = make([]uint32, len(changes))
   505  				for i, ch := range changes {
   506  					change, err := strconv.ParseUint(ch, 10, 32)
   507  					if err != nil {
   508  						return nil, err
   509  					}
   510  					descriptor.ChangeIndexes[i] = uint32(change)
   511  
   512  				}
   513  			} else {
   514  				// default to {0,1}
   515  				descriptor.ChangeIndexes = []uint32{0, 1}
   516  			}
   517  
   518  		}
   519  		return &descriptor, nil
   520  	}
   521  	return p.xpubDescriptorFromXpub(xpub)
   522  
   523  }
   524  
   525  // DeriveAddressDescriptors derives address descriptors from given xpub for listed indexes
   526  func (p *BitcoinLikeParser) DeriveAddressDescriptors(descriptor *bchain.XpubDescriptor, change uint32, indexes []uint32) ([]bchain.AddressDescriptor, error) {
   527  	ad := make([]bchain.AddressDescriptor, len(indexes))
   528  	changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Derive(change)
   529  	if err != nil {
   530  		return nil, err
   531  	}
   532  	for i, index := range indexes {
   533  		indexExtKey, err := changeExtKey.Derive(index)
   534  		if err != nil {
   535  			return nil, err
   536  		}
   537  		ad[i], err = p.addrDescFromExtKey(indexExtKey, descriptor)
   538  		if err != nil {
   539  			return nil, err
   540  		}
   541  	}
   542  	return ad, nil
   543  }
   544  
   545  // DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
   546  func (p *BitcoinLikeParser) DeriveAddressDescriptorsFromTo(descriptor *bchain.XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
   547  	if toIndex <= fromIndex {
   548  		return nil, errors.New("toIndex<=fromIndex")
   549  	}
   550  	changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Derive(change)
   551  	if err != nil {
   552  		return nil, err
   553  	}
   554  	ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
   555  	for index := fromIndex; index < toIndex; index++ {
   556  		indexExtKey, err := changeExtKey.Derive(index)
   557  		if err != nil {
   558  			return nil, err
   559  		}
   560  		ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey, descriptor)
   561  		if err != nil {
   562  			return nil, err
   563  		}
   564  	}
   565  	return ad, nil
   566  }
   567  
   568  // DerivationBasePath returns base path of xpub
   569  func (p *BitcoinLikeParser) DerivationBasePath(descriptor *bchain.XpubDescriptor) (string, error) {
   570  	var c string
   571  	extKey := descriptor.ExtKey.(*hdkeychain.ExtendedKey)
   572  	cn := extKey.ChildNum()
   573  	if cn >= 0x80000000 {
   574  		cn -= 0x80000000
   575  		c = "'"
   576  	}
   577  	c = strconv.Itoa(int(cn)) + c
   578  	if extKey.Depth() != 3 {
   579  		return "unknown/" + c, nil
   580  	}
   581  	return "m/" + descriptor.Bip + "'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
   582  }