blockbook/bchain/coins/bch/bcashparser.go

189 lines
4.8 KiB
Go

package bch
import (
"fmt"
"github.com/martinboehm/bchutil"
"github.com/martinboehm/btcutil"
"github.com/martinboehm/btcutil/chaincfg"
"github.com/martinboehm/btcutil/txscript"
"github.com/schancel/cashaddr-converter/address"
"spacecruft.org/spacecruft/blockbook/bchain"
"spacecruft.org/spacecruft/blockbook/bchain/coins/btc"
)
// AddressFormat type is used to specify different formats of address
type AddressFormat = uint8
const (
// Legacy AddressFormat is the same as Bitcoin
Legacy AddressFormat = iota
// CashAddr AddressFormat is new Bitcoin Cash standard
CashAddr
)
const (
// MainNetPrefix is CashAddr prefix for mainnet
MainNetPrefix = "bitcoincash:"
// TestNetPrefix is CashAddr prefix for testnet
TestNetPrefix = "bchtest:"
// RegTestPrefix is CashAddr prefix for regtest
RegTestPrefix = "bchreg:"
)
var (
// MainNetParams are parser parameters for mainnet
MainNetParams chaincfg.Params
// TestNetParams are parser parameters for testnet
TestNetParams chaincfg.Params
// RegtestParams are parser parameters for regtest
RegtestParams chaincfg.Params
)
func init() {
MainNetParams = chaincfg.MainNetParams
MainNetParams.Net = bchutil.MainnetMagic
TestNetParams = chaincfg.TestNet3Params
TestNetParams.Net = bchutil.TestnetMagic
RegtestParams = chaincfg.RegressionNetParams
RegtestParams.Net = bchutil.Regtestmagic
}
// BCashParser handle
type BCashParser struct {
*btc.BitcoinParser
AddressFormat AddressFormat
}
// NewBCashParser returns new BCashParser instance
func NewBCashParser(params *chaincfg.Params, c *btc.Configuration) (*BCashParser, error) {
var format AddressFormat
switch c.AddressFormat {
case "":
fallthrough
case "cashaddr":
format = CashAddr
case "legacy":
format = Legacy
default:
return nil, fmt.Errorf("Unknown address format: %s", c.AddressFormat)
}
p := &BCashParser{
BitcoinParser: btc.NewBitcoinParser(params, c),
AddressFormat: format,
}
p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
return p, nil
}
// GetChainParams contains network parameters for the main Bitcoin Cash network,
// the regression test Bitcoin Cash network, the test Bitcoin Cash network and
// the simulation test Bitcoin Cash network, in this order
func GetChainParams(chain string) *chaincfg.Params {
if !chaincfg.IsRegistered(&MainNetParams) {
err := chaincfg.Register(&MainNetParams)
if err == nil {
err = chaincfg.Register(&TestNetParams)
}
if err == nil {
err = chaincfg.Register(&RegtestParams)
}
if err != nil {
panic(err)
}
}
switch chain {
case "test":
return &TestNetParams
case "regtest":
return &RegtestParams
default:
return &MainNetParams
}
}
// GetAddrDescFromAddress returns internal address representation of given address
func (p *BCashParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) {
return p.addressToOutputScript(address)
}
// addressToOutputScript converts bitcoin address to ScriptPubKey
func (p *BCashParser) addressToOutputScript(address string) ([]byte, error) {
if isCashAddr(address) {
da, err := bchutil.DecodeAddress(address, p.Params)
if err != nil {
return nil, err
}
script, err := bchutil.PayToAddrScript(da)
if err != nil {
return nil, err
}
return script, nil
}
da, err := btcutil.DecodeAddress(address, p.Params)
if err != nil {
return nil, err
}
script, err := txscript.PayToAddrScript(da)
if err != nil {
return nil, err
}
return script, nil
}
func isCashAddr(addr string) bool {
n := len(addr)
switch {
case n > len(MainNetPrefix) && addr[0:len(MainNetPrefix)] == MainNetPrefix:
return true
case n > len(TestNetPrefix) && addr[0:len(TestNetPrefix)] == TestNetPrefix:
return true
case n > len(RegTestPrefix) && addr[0:len(RegTestPrefix)] == RegTestPrefix:
return true
}
return false
}
// outputScriptToAddresses converts ScriptPubKey to bitcoin addresses
func (p *BCashParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
// convert possible P2PK script to P2PK, which bchutil can process
var err error
script, err = txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, script)
if err != nil {
return nil, false, err
}
a, err := bchutil.ExtractPkScriptAddrs(script, p.Params)
if err != nil {
// do not return unknown script type error as error
if err.Error() == "unknown script type" {
// try OP_RETURN script
or := p.TryParseOPReturn(script)
if or != "" {
return []string{or}, false, nil
}
return []string{}, false, nil
}
return nil, false, err
}
// EncodeAddress returns CashAddr address
addr := a.EncodeAddress()
if p.AddressFormat == Legacy {
da, err := address.NewFromString(addr)
if err != nil {
return nil, false, err
}
ca, err := da.Legacy()
if err != nil {
return nil, false, err
}
addr, err = ca.Encode()
if err != nil {
return nil, false, err
}
}
return []string{addr}, len(addr) > 0, nil
}