github.com/cryptohub-digital/blockbook-fork@v0.0.0-20230713133354-673c927af7f1/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-fork/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 }