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 }