194 lines
4.4 KiB
Go
194 lines
4.4 KiB
Go
package tron
|
|
|
|
import (
|
|
"bytes"
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"math/big"
|
|
"strings"
|
|
|
|
"github.com/tech/sendico/pkg/merrors"
|
|
)
|
|
|
|
const tronHexPrefix = "0x"
|
|
|
|
var base58Alphabet = []byte("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz")
|
|
|
|
func normalizeAddress(address string) (string, error) {
|
|
trimmed := strings.TrimSpace(address)
|
|
if trimmed == "" {
|
|
return "", merrors.InvalidArgument("address is required")
|
|
}
|
|
if strings.HasPrefix(trimmed, tronHexPrefix) || isHexString(trimmed) {
|
|
return hexToBase58(trimmed)
|
|
}
|
|
decoded, err := base58Decode(trimmed)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := validateChecksum(decoded); err != nil {
|
|
return "", err
|
|
}
|
|
return base58Encode(decoded), nil
|
|
}
|
|
|
|
func rpcAddress(address string) (string, error) {
|
|
trimmed := strings.TrimSpace(address)
|
|
if trimmed == "" {
|
|
return "", merrors.InvalidArgument("address is required")
|
|
}
|
|
if strings.HasPrefix(trimmed, tronHexPrefix) || isHexString(trimmed) {
|
|
return normalizeHexRPC(trimmed)
|
|
}
|
|
return base58ToHex(trimmed)
|
|
}
|
|
|
|
func hexToBase58(address string) (string, error) {
|
|
bytesAddr, err := parseHexAddress(address)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
payload := append(bytesAddr, checksum(bytesAddr)...)
|
|
return base58Encode(payload), nil
|
|
}
|
|
|
|
func base58ToHex(address string) (string, error) {
|
|
decoded, err := base58Decode(address)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if err := validateChecksum(decoded); err != nil {
|
|
return "", err
|
|
}
|
|
return tronHexPrefix + hex.EncodeToString(decoded[1:21]), nil
|
|
}
|
|
|
|
func parseHexAddress(address string) ([]byte, error) {
|
|
trimmed := strings.TrimPrefix(strings.TrimSpace(address), tronHexPrefix)
|
|
if trimmed == "" {
|
|
return nil, merrors.InvalidArgument("address is required")
|
|
}
|
|
if len(trimmed)%2 == 1 {
|
|
trimmed = "0" + trimmed
|
|
}
|
|
decoded, err := hex.DecodeString(trimmed)
|
|
if err != nil {
|
|
return nil, merrors.InvalidArgument("invalid hex address")
|
|
}
|
|
switch len(decoded) {
|
|
case 20:
|
|
prefixed := make([]byte, 21)
|
|
prefixed[0] = 0x41
|
|
copy(prefixed[1:], decoded)
|
|
return prefixed, nil
|
|
case 21:
|
|
if decoded[0] != 0x41 {
|
|
return nil, merrors.InvalidArgument("invalid tron address prefix")
|
|
}
|
|
return decoded, nil
|
|
default:
|
|
return nil, merrors.InvalidArgument(fmt.Sprintf("invalid tron address length %d", len(decoded)))
|
|
}
|
|
}
|
|
|
|
func normalizeHexRPC(address string) (string, error) {
|
|
decoded, err := parseHexAddress(address)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return tronHexPrefix + hex.EncodeToString(decoded[1:21]), nil
|
|
}
|
|
|
|
func validateChecksum(decoded []byte) error {
|
|
if len(decoded) != 25 {
|
|
return merrors.InvalidArgument("invalid tron address length")
|
|
}
|
|
payload := decoded[:21]
|
|
expected := checksum(payload)
|
|
if !bytes.Equal(expected, decoded[21:]) {
|
|
return merrors.InvalidArgument("invalid tron address checksum")
|
|
}
|
|
if payload[0] != 0x41 {
|
|
return merrors.InvalidArgument("invalid tron address prefix")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func checksum(payload []byte) []byte {
|
|
first := sha256.Sum256(payload)
|
|
second := sha256.Sum256(first[:])
|
|
return second[:4]
|
|
}
|
|
|
|
func base58Encode(input []byte) string {
|
|
if len(input) == 0 {
|
|
return ""
|
|
}
|
|
x := new(big.Int).SetBytes(input)
|
|
base := big.NewInt(58)
|
|
zero := big.NewInt(0)
|
|
mod := new(big.Int)
|
|
|
|
encoded := make([]byte, 0, len(input))
|
|
for x.Cmp(zero) > 0 {
|
|
x.DivMod(x, base, mod)
|
|
encoded = append(encoded, base58Alphabet[mod.Int64()])
|
|
}
|
|
for _, b := range input {
|
|
if b != 0 {
|
|
break
|
|
}
|
|
encoded = append(encoded, base58Alphabet[0])
|
|
}
|
|
reverse(encoded)
|
|
return string(encoded)
|
|
}
|
|
|
|
func base58Decode(input string) ([]byte, error) {
|
|
result := big.NewInt(0)
|
|
base := big.NewInt(58)
|
|
|
|
for i := 0; i < len(input); i++ {
|
|
idx := bytes.IndexByte(base58Alphabet, input[i])
|
|
if idx < 0 {
|
|
return nil, merrors.InvalidArgument("invalid base58 address")
|
|
}
|
|
result.Mul(result, base)
|
|
result.Add(result, big.NewInt(int64(idx)))
|
|
}
|
|
|
|
decoded := result.Bytes()
|
|
zeroCount := 0
|
|
for zeroCount < len(input) && input[zeroCount] == base58Alphabet[0] {
|
|
zeroCount++
|
|
}
|
|
if zeroCount > 0 {
|
|
decoded = append(make([]byte, zeroCount), decoded...)
|
|
}
|
|
return decoded, nil
|
|
}
|
|
|
|
func reverse(data []byte) {
|
|
for i, j := 0, len(data)-1; i < j; i, j = i+1, j-1 {
|
|
data[i], data[j] = data[j], data[i]
|
|
}
|
|
}
|
|
|
|
func isHexString(value string) bool {
|
|
trimmed := strings.TrimPrefix(strings.TrimSpace(value), tronHexPrefix)
|
|
if trimmed == "" {
|
|
return false
|
|
}
|
|
for _, r := range trimmed {
|
|
switch {
|
|
case r >= '0' && r <= '9':
|
|
case r >= 'a' && r <= 'f':
|
|
case r >= 'A' && r <= 'F':
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|