189 lines
4.8 KiB
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
|
|
}
|