github.com/cryptohub-digital/blockbook@v0.3.5-0.20240403155730-99ab40b9104c/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/cryptohub-digital/blockbook/bchain"
    16  	"github.com/juju/errors"
    17  	"github.com/martinboehm/btcd/blockchain"
    18  	"github.com/martinboehm/btcd/btcec"
    19  	"github.com/martinboehm/btcd/wire"
    20  	"github.com/martinboehm/btcutil"
    21  	"github.com/martinboehm/btcutil/chaincfg"
    22  	"github.com/martinboehm/btcutil/hdkeychain"
    23  	"github.com/martinboehm/btcutil/txscript"
    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  		}
   235  	}
   236  	vout := make([]bchain.Vout, len(t.TxOut))
   237  	for i, out := range t.TxOut {
   238  		addrs := []string{}
   239  		if parseAddresses {
   240  			addrs, _, _ = p.OutputScriptToAddressesFunc(out.PkScript)
   241  		}
   242  		s := bchain.ScriptPubKey{
   243  			Hex:       hex.EncodeToString(out.PkScript),
   244  			Addresses: addrs,
   245  			// missing: Asm,
   246  			// missing: Type,
   247  		}
   248  		var vs big.Int
   249  		vs.SetInt64(out.Value)
   250  		vout[i] = bchain.Vout{
   251  			ValueSat:     vs,
   252  			N:            uint32(i),
   253  			ScriptPubKey: s,
   254  		}
   255  	}
   256  	tx := bchain.Tx{
   257  		Txid:     t.TxHash().String(),
   258  		Version:  t.Version,
   259  		LockTime: t.LockTime,
   260  		VSize:    vSize,
   261  		Vin:      vin,
   262  		Vout:     vout,
   263  		// skip: BlockHash,
   264  		// skip: Confirmations,
   265  		// skip: Time,
   266  		// skip: Blocktime,
   267  	}
   268  	return tx
   269  }
   270  
   271  // ParseTx parses byte array containing transaction and returns Tx struct
   272  func (p *BitcoinLikeParser) ParseTx(b []byte) (*bchain.Tx, error) {
   273  	t := wire.MsgTx{}
   274  	r := bytes.NewReader(b)
   275  	if err := t.Deserialize(r); err != nil {
   276  		return nil, err
   277  	}
   278  	tx := p.TxFromMsgTx(&t, true)
   279  	tx.Hex = hex.EncodeToString(b)
   280  	return &tx, nil
   281  }
   282  
   283  // ParseBlock parses raw block to our Block struct
   284  func (p *BitcoinLikeParser) ParseBlock(b []byte) (*bchain.Block, error) {
   285  	w := wire.MsgBlock{}
   286  	r := bytes.NewReader(b)
   287  
   288  	if err := w.Deserialize(r); err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	txs := make([]bchain.Tx, len(w.Transactions))
   293  	for ti, t := range w.Transactions {
   294  		txs[ti] = p.TxFromMsgTx(t, false)
   295  	}
   296  
   297  	return &bchain.Block{
   298  		BlockHeader: bchain.BlockHeader{
   299  			Size: len(b),
   300  			Time: w.Header.Timestamp.Unix(),
   301  		},
   302  		Txs: txs,
   303  	}, nil
   304  }
   305  
   306  // PackTx packs transaction to byte array
   307  func (p *BitcoinLikeParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) ([]byte, error) {
   308  	buf := make([]byte, 4+vlq.MaxLen64+len(tx.Hex)/2)
   309  	binary.BigEndian.PutUint32(buf[0:4], height)
   310  	vl := vlq.PutInt(buf[4:4+vlq.MaxLen64], blockTime)
   311  	hl, err := hex.Decode(buf[4+vl:], []byte(tx.Hex))
   312  	return buf[0 : 4+vl+hl], err
   313  }
   314  
   315  // UnpackTx unpacks transaction from byte array
   316  func (p *BitcoinLikeParser) UnpackTx(buf []byte) (*bchain.Tx, uint32, error) {
   317  	height := binary.BigEndian.Uint32(buf)
   318  	bt, l := vlq.Int(buf[4:])
   319  	tx, err := p.ParseTx(buf[4+l:])
   320  	if err != nil {
   321  		return nil, 0, err
   322  	}
   323  	tx.Blocktime = bt
   324  
   325  	return tx, height, nil
   326  }
   327  
   328  // MinimumCoinbaseConfirmations returns minimum number of confirmations a coinbase transaction must have before it can be spent
   329  func (p *BitcoinLikeParser) MinimumCoinbaseConfirmations() int {
   330  	return p.minimumCoinbaseConfirmations
   331  }
   332  
   333  // SupportsVSize returns true if vsize of a transaction should be computed and returned by API
   334  func (p *BitcoinLikeParser) SupportsVSize() bool {
   335  	return p.VSizeSupport
   336  }
   337  
   338  var tapTweakTagHash = sha256.Sum256([]byte("TapTweak"))
   339  
   340  func tapTweakHash(msg []byte) []byte {
   341  	tagLen := len(tapTweakTagHash)
   342  	m := make([]byte, tagLen*2+len(msg))
   343  	copy(m[:tagLen], tapTweakTagHash[:])
   344  	copy(m[tagLen:tagLen*2], tapTweakTagHash[:])
   345  	copy(m[tagLen*2:], msg)
   346  	h := sha256.Sum256(m)
   347  	return h[:]
   348  }
   349  
   350  func (p *BitcoinLikeParser) taprootAddrFromExtKey(extKey *hdkeychain.ExtendedKey) (*btcutil.AddressWitnessTaproot, error) {
   351  	curve := btcec.S256()
   352  	t := new(big.Int)
   353  
   354  	// tweak the derived pubkey to the output pub key according to https://en.bitcoin.it/wiki/BIP_0341
   355  	// and https://github.com/bitcoin/bips/blob/master/bip-0086.mediawiki
   356  	derived_key := extKey.PubKeyBytes()[1:]
   357  
   358  	t.SetBytes(tapTweakHash(derived_key))
   359  	// Fail if t >=order of the base point
   360  	if t.Cmp(curve.N) >= 0 {
   361  		return nil, errors.New("greater than or equal to curve order")
   362  	}
   363  	// Q = point_add(lift_x(int_from_bytes(pubkey)), point_mul(G, t))
   364  	ipx, ipy, err := btcec.LiftX(derived_key)
   365  	if err != nil {
   366  		return nil, err
   367  	}
   368  	tGx, tGy := curve.ScalarBaseMult(t.Bytes())
   369  	output_pubkey, _ := curve.Add(ipx, ipy, tGx, tGy)
   370  	//
   371  	b := output_pubkey.Bytes()
   372  	// the x coordinate on the curve can be a number small enough that it does not need 32 bytes required for the output script
   373  	if len(b) < 32 {
   374  		b = make([]byte, 32)
   375  		output_pubkey.FillBytes(b)
   376  	}
   377  	return btcutil.NewAddressWitnessTaproot(b, p.Params)
   378  }
   379  
   380  func (p *BitcoinLikeParser) addrDescFromExtKey(extKey *hdkeychain.ExtendedKey, descriptor *bchain.XpubDescriptor) (bchain.AddressDescriptor, error) {
   381  	var a btcutil.Address
   382  	var err error
   383  	switch descriptor.Type {
   384  	case bchain.P2PKH:
   385  		a, err = extKey.Address(p.Params)
   386  	case bchain.P2SHWPKH:
   387  		// redeemScript <witness version: OP_0><len pubKeyHash: 20><20-byte-pubKeyHash>
   388  		pubKeyHash := btcutil.Hash160(extKey.PubKeyBytes())
   389  		redeemScript := make([]byte, len(pubKeyHash)+2)
   390  		redeemScript[0] = 0
   391  		redeemScript[1] = byte(len(pubKeyHash))
   392  		copy(redeemScript[2:], pubKeyHash)
   393  		hash := btcutil.Hash160(redeemScript)
   394  		a, err = btcutil.NewAddressScriptHashFromHash(hash, p.Params)
   395  	case bchain.P2WPKH:
   396  		a, err = btcutil.NewAddressWitnessPubKeyHash(btcutil.Hash160(extKey.PubKeyBytes()), p.Params)
   397  	case bchain.P2TR:
   398  		a, err = p.taprootAddrFromExtKey(extKey)
   399  	default:
   400  		return nil, errors.New("Unsupported xpub descriptor type")
   401  	}
   402  	if err != nil {
   403  		return nil, err
   404  	}
   405  	return txscript.PayToAddrScript(a)
   406  }
   407  
   408  func (p *BitcoinLikeParser) xpubDescriptorFromXpub(xpub string) (*bchain.XpubDescriptor, error) {
   409  	var descriptor bchain.XpubDescriptor
   410  	extKey, err := hdkeychain.NewKeyFromString(xpub, p.Params.Base58CksumHasher)
   411  	if err != nil {
   412  		return nil, err
   413  	}
   414  	descriptor.Xpub = xpub
   415  	descriptor.XpubDescriptor = xpub
   416  	if extKey.Version() == p.XPubMagicSegwitP2sh {
   417  		descriptor.Type = bchain.P2SHWPKH
   418  		descriptor.Bip = "49"
   419  	} else if extKey.Version() == p.XPubMagicSegwitNative {
   420  		descriptor.Type = bchain.P2WPKH
   421  		descriptor.Bip = "84"
   422  	} else {
   423  		descriptor.Type = bchain.P2PKH
   424  		descriptor.Bip = "44"
   425  	}
   426  	descriptor.ChangeIndexes = []uint32{0, 1}
   427  	descriptor.ExtKey = extKey
   428  	return &descriptor, nil
   429  }
   430  
   431  var (
   432  	xpubDesriptorRegex     *regexp.Regexp
   433  	typeSubexpIndex        int
   434  	bipSubexpIndex         int
   435  	xpubSubexpIndex        int
   436  	changeSubexpIndex      int
   437  	changeList1SubexpIndex int
   438  	changeList2SubexpIndex int
   439  )
   440  
   441  func init() {
   442  	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+))/\*)?\)+`)
   443  	typeSubexpIndex = xpubDesriptorRegex.SubexpIndex("type")
   444  	bipSubexpIndex = xpubDesriptorRegex.SubexpIndex("bip")
   445  	xpubSubexpIndex = xpubDesriptorRegex.SubexpIndex("xpub")
   446  	changeList1SubexpIndex = xpubDesriptorRegex.SubexpIndex("changelist1")
   447  	changeList2SubexpIndex = xpubDesriptorRegex.SubexpIndex("changelist2")
   448  	changeSubexpIndex = xpubDesriptorRegex.SubexpIndex("change")
   449  	if changeSubexpIndex < 0 {
   450  		panic("Invalid bitcoinparser xpubDesriptorRegex")
   451  	}
   452  }
   453  
   454  // ParseXpub parses xpub (or xpub descriptor) and returns XpubDescriptor
   455  func (p *BitcoinLikeParser) ParseXpub(xpub string) (*bchain.XpubDescriptor, error) {
   456  	match := xpubDesriptorRegex.FindStringSubmatch(xpub)
   457  	if len(match) > changeSubexpIndex {
   458  		var descriptor bchain.XpubDescriptor
   459  		descriptor.XpubDescriptor = xpub
   460  		m := match[typeSubexpIndex]
   461  		switch m {
   462  		case "pkh":
   463  			descriptor.Type = bchain.P2PKH
   464  			descriptor.Bip = "44"
   465  		case "sh(wpkh":
   466  			descriptor.Type = bchain.P2SHWPKH
   467  			descriptor.Bip = "49"
   468  		case "wpkh":
   469  			descriptor.Type = bchain.P2WPKH
   470  			descriptor.Bip = "84"
   471  		case "tr":
   472  			descriptor.Type = bchain.P2TR
   473  			descriptor.Bip = "86"
   474  		default:
   475  			return nil, errors.Errorf("Xpub descriptor %s is not supported", m)
   476  		}
   477  		if len(match[bipSubexpIndex]) > 0 {
   478  			descriptor.Bip = match[bipSubexpIndex]
   479  		}
   480  		descriptor.Xpub = match[xpubSubexpIndex]
   481  		extKey, err := hdkeychain.NewKeyFromString(descriptor.Xpub, p.Params.Base58CksumHasher)
   482  		if err != nil {
   483  			return nil, err
   484  		}
   485  		descriptor.ExtKey = extKey
   486  		if len(match[changeSubexpIndex]) > 0 {
   487  			change, err := strconv.ParseUint(match[changeSubexpIndex], 10, 32)
   488  			if err != nil {
   489  				return nil, err
   490  			}
   491  			descriptor.ChangeIndexes = []uint32{uint32(change)}
   492  		} else {
   493  			if len(match[changeList1SubexpIndex]) > 0 || len(match[changeList2SubexpIndex]) > 0 {
   494  				var changes []string
   495  				if len(match[changeList1SubexpIndex]) > 0 {
   496  					changes = strings.Split(match[changeList1SubexpIndex], ",")
   497  				} else {
   498  					changes = strings.Split(match[changeList2SubexpIndex], ";")
   499  				}
   500  				if len(changes) == 0 {
   501  					return nil, errors.New("Invalid xpub descriptor, cannot parse change")
   502  				}
   503  				descriptor.ChangeIndexes = make([]uint32, len(changes))
   504  				for i, ch := range changes {
   505  					change, err := strconv.ParseUint(ch, 10, 32)
   506  					if err != nil {
   507  						return nil, err
   508  					}
   509  					descriptor.ChangeIndexes[i] = uint32(change)
   510  
   511  				}
   512  			} else {
   513  				// default to {0,1}
   514  				descriptor.ChangeIndexes = []uint32{0, 1}
   515  			}
   516  
   517  		}
   518  		return &descriptor, nil
   519  	}
   520  	return p.xpubDescriptorFromXpub(xpub)
   521  
   522  }
   523  
   524  // DeriveAddressDescriptors derives address descriptors from given xpub for listed indexes
   525  func (p *BitcoinLikeParser) DeriveAddressDescriptors(descriptor *bchain.XpubDescriptor, change uint32, indexes []uint32) ([]bchain.AddressDescriptor, error) {
   526  	ad := make([]bchain.AddressDescriptor, len(indexes))
   527  	changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Derive(change)
   528  	if err != nil {
   529  		return nil, err
   530  	}
   531  	for i, index := range indexes {
   532  		indexExtKey, err := changeExtKey.Derive(index)
   533  		if err != nil {
   534  			return nil, err
   535  		}
   536  		ad[i], err = p.addrDescFromExtKey(indexExtKey, descriptor)
   537  		if err != nil {
   538  			return nil, err
   539  		}
   540  	}
   541  	return ad, nil
   542  }
   543  
   544  // DeriveAddressDescriptorsFromTo derives address descriptors from given xpub for addresses in index range
   545  func (p *BitcoinLikeParser) DeriveAddressDescriptorsFromTo(descriptor *bchain.XpubDescriptor, change uint32, fromIndex uint32, toIndex uint32) ([]bchain.AddressDescriptor, error) {
   546  	if toIndex <= fromIndex {
   547  		return nil, errors.New("toIndex<=fromIndex")
   548  	}
   549  	changeExtKey, err := descriptor.ExtKey.(*hdkeychain.ExtendedKey).Derive(change)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	ad := make([]bchain.AddressDescriptor, toIndex-fromIndex)
   554  	for index := fromIndex; index < toIndex; index++ {
   555  		indexExtKey, err := changeExtKey.Derive(index)
   556  		if err != nil {
   557  			return nil, err
   558  		}
   559  		ad[index-fromIndex], err = p.addrDescFromExtKey(indexExtKey, descriptor)
   560  		if err != nil {
   561  			return nil, err
   562  		}
   563  	}
   564  	return ad, nil
   565  }
   566  
   567  // DerivationBasePath returns base path of xpub
   568  func (p *BitcoinLikeParser) DerivationBasePath(descriptor *bchain.XpubDescriptor) (string, error) {
   569  	var c string
   570  	extKey := descriptor.ExtKey.(*hdkeychain.ExtendedKey)
   571  	cn := extKey.ChildNum()
   572  	if cn >= 0x80000000 {
   573  		cn -= 0x80000000
   574  		c = "'"
   575  	}
   576  	c = strconv.Itoa(int(cn)) + c
   577  	if extKey.Depth() != 3 {
   578  		return "unknown/" + c, nil
   579  	}
   580  	return "m/" + descriptor.Bip + "'/" + strconv.Itoa(int(p.Slip44)) + "'/" + c, nil
   581  }