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" "github.com/trezor/blockbook/bchain" "github.com/trezor/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 }