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 }