github.com/trezor/blockbook@v0.4.1-0.20240328132726-e9a08582ee2c/bchain/coins/ecash/ecashparser.go (about)

     1  package ecash
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/pirk/ecashutil"
     7  	"github.com/martinboehm/btcutil"
     8  	"github.com/martinboehm/btcutil/chaincfg"
     9  	"github.com/martinboehm/btcutil/txscript"
    10  	"github.com/pirk/ecashaddr-converter/address"
    11  	"github.com/trezor/blockbook/bchain"
    12  	"github.com/trezor/blockbook/bchain/coins/btc"
    13  )
    14  
    15  // AddressFormat type is used to specify different formats of address
    16  type AddressFormat = uint8
    17  
    18  const (
    19  	// Legacy AddressFormat is the same as Bitcoin
    20  	Legacy AddressFormat = iota
    21  	// CashAddr AddressFormat is new eCash standard
    22  	CashAddr
    23  )
    24  
    25  const (
    26  	// MainNetPrefix is CashAddr prefix for mainnet
    27  	MainNetPrefix = "ecash:"
    28  	// TestNetPrefix is CashAddr prefix for testnet
    29  	TestNetPrefix = "ectest:"
    30  	// RegTestPrefix is CashAddr prefix for regtest
    31  	RegTestPrefix = "ecreg:"
    32  )
    33  
    34  var (
    35  	// MainNetParams are parser parameters for mainnet
    36  	MainNetParams chaincfg.Params
    37  	// TestNetParams are parser parameters for testnet
    38  	TestNetParams chaincfg.Params
    39  	// RegtestParams are parser parameters for regtest
    40  	RegtestParams chaincfg.Params
    41  )
    42  
    43  func init() {
    44  	MainNetParams = chaincfg.MainNetParams
    45  	MainNetParams.Net = ecashutil.MainnetMagic
    46  
    47  	TestNetParams = chaincfg.TestNet3Params
    48  	TestNetParams.Net = ecashutil.TestnetMagic
    49  
    50  	RegtestParams = chaincfg.RegressionNetParams
    51  	RegtestParams.Net = ecashutil.Regtestmagic
    52  }
    53  
    54  // ECashParser handle
    55  type ECashParser struct {
    56  	*btc.BitcoinLikeParser
    57  	AddressFormat AddressFormat
    58  }
    59  
    60  // NewECashParser returns new ECashParser instance
    61  func NewECashParser(params *chaincfg.Params, c *btc.Configuration) (*ECashParser, error) {
    62  	var format AddressFormat
    63  	switch c.AddressFormat {
    64  	case "":
    65  		fallthrough
    66  	case "cashaddr":
    67  		format = CashAddr
    68  	case "legacy":
    69  		format = Legacy
    70  	default:
    71  		return nil, fmt.Errorf("Unknown address format: %s", c.AddressFormat)
    72  	}
    73  	p := &ECashParser{
    74  		BitcoinLikeParser: btc.NewBitcoinLikeParser(params, c),
    75  		AddressFormat:     format,
    76  	}
    77  	p.OutputScriptToAddressesFunc = p.outputScriptToAddresses
    78  	p.AmountDecimalPoint = 2
    79  	return p, nil
    80  }
    81  
    82  // GetChainParams contains network parameters for the main eCash network,
    83  // the regression test eCash network, the test eCash network and
    84  // the simulation test eCash network, in this order
    85  func GetChainParams(chain string) *chaincfg.Params {
    86  	if !chaincfg.IsRegistered(&MainNetParams) {
    87  		err := chaincfg.Register(&MainNetParams)
    88  		if err == nil {
    89  			err = chaincfg.Register(&TestNetParams)
    90  		}
    91  		if err == nil {
    92  			err = chaincfg.Register(&RegtestParams)
    93  		}
    94  		if err != nil {
    95  			panic(err)
    96  		}
    97  	}
    98  	switch chain {
    99  	case "test":
   100  		return &TestNetParams
   101  	case "regtest":
   102  		return &RegtestParams
   103  	default:
   104  		return &MainNetParams
   105  	}
   106  }
   107  
   108  // GetAddrDescFromAddress returns internal address representation of given address
   109  func (p *ECashParser) GetAddrDescFromAddress(address string) (bchain.AddressDescriptor, error) {
   110  	return p.addressToOutputScript(address)
   111  }
   112  
   113  // addressToOutputScript converts address to ScriptPubKey
   114  func (p *ECashParser) addressToOutputScript(address string) ([]byte, error) {
   115  	if isCashAddr(address) {
   116  		da, err := ecashutil.DecodeAddress(address, p.Params)
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		script, err := ecashutil.PayToAddrScript(da)
   121  		if err != nil {
   122  			return nil, err
   123  		}
   124  		return script, nil
   125  	}
   126  	da, err := btcutil.DecodeAddress(address, p.Params)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  	script, err := txscript.PayToAddrScript(da)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	return script, nil
   135  }
   136  
   137  func isCashAddr(addr string) bool {
   138  	n := len(addr)
   139  	switch {
   140  	case n > len(MainNetPrefix) && addr[0:len(MainNetPrefix)] == MainNetPrefix:
   141  		return true
   142  	case n > len(TestNetPrefix) && addr[0:len(TestNetPrefix)] == TestNetPrefix:
   143  		return true
   144  	case n > len(RegTestPrefix) && addr[0:len(RegTestPrefix)] == RegTestPrefix:
   145  		return true
   146  	}
   147  
   148  	return false
   149  }
   150  
   151  // outputScriptToAddresses converts ScriptPubKey to bitcoin addresses
   152  func (p *ECashParser) outputScriptToAddresses(script []byte) ([]string, bool, error) {
   153  	// convert possible P2PK script to P2PK, which ecashutil can process
   154  	var err error
   155  	script, err = txscript.ConvertP2PKtoP2PKH(p.Params.Base58CksumHasher, script)
   156  	if err != nil {
   157  		return nil, false, err
   158  	}
   159  	a, err := ecashutil.ExtractPkScriptAddrs(script, p.Params)
   160  	if err != nil {
   161  		// do not return unknown script type error as error
   162  		if err.Error() == "unknown script type" {
   163  			// try OP_RETURN script
   164  			or := p.TryParseOPReturn(script)
   165  			if or != "" {
   166  				return []string{or}, false, nil
   167  			}
   168  			return []string{}, false, nil
   169  		}
   170  		return nil, false, err
   171  	}
   172  	// EncodeAddress returns CashAddr address
   173  	addr := a.EncodeAddress()
   174  	if p.AddressFormat == Legacy {
   175  		da, err := address.NewFromString(addr)
   176  		if err != nil {
   177  			return nil, false, err
   178  		}
   179  		ca, err := da.Legacy()
   180  		if err != nil {
   181  			return nil, false, err
   182  		}
   183  		addr, err = ca.Encode()
   184  		if err != nil {
   185  			return nil, false, err
   186  		}
   187  	}
   188  	return []string{addr}, len(addr) > 0, nil
   189  }