github.com/chain5j/chain5j-pkg@v1.0.7/crypto/icap/icap.go (about)

     1  // Package icap
     2  //
     3  // @author: xwc1125
     4  // @date: 2021/4/14
     5  package icap
     6  
     7  import (
     8  	"errors"
     9  	"log"
    10  	"math/big"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/chain5j/chain5j-pkg/crypto/base/base36"
    15  	"github.com/chain5j/chain5j-pkg/types"
    16  )
    17  
    18  var (
    19  	errICAPLength      = errors.New("invalid ICAP length")
    20  	errICAPEncoding    = errors.New("invalid ICAP encoding")
    21  	errICAPChecksum    = errors.New("invalid ICAP checksum")
    22  	errICAPCountryCode = errors.New("invalid ICAP country code")
    23  	errICAPAssetIdent  = errors.New("invalid ICAP asset identifier")
    24  	errICAPInstCode    = errors.New("invalid ICAP institution code")
    25  	errICAPClientIdent = errors.New("invalid ICAP client identifier")
    26  )
    27  
    28  var (
    29  	Big1  = big.NewInt(1)
    30  	Big97 = big.NewInt(97)
    31  	Big98 = big.NewInt(98)
    32  )
    33  
    34  func ToICAP(customer Customer) (*IBanInfo, error) {
    35  	enc := base36.EncodeBytes(customer.Customer())
    36  	currencyLen := len(customer.Currency())
    37  	orgCodeLen := len(customer.OrgCode())
    38  	// 减去2位校验码
    39  	interLen := customer.ResultLen() - currencyLen - orgCodeLen - 2 - len(enc)
    40  	if interLen > 0 {
    41  		enc = join(strings.Repeat("0", interLen), enc)
    42  	}
    43  	icap := join(customer.Currency(), checkDigits(customer.Currency(), customer.OrgCode(), enc), customer.OrgCode(), enc)
    44  	return NewIBanInfo(currencyLen, orgCodeLen, len(customer.customer), icap), nil
    45  }
    46  
    47  func ParseICAP(iban IBanInfo) (*Customer, error) {
    48  	if err := ValidCheckSumWithLen(iban.currencyLen, iban.orgCodeLen, iban.iban); err != nil {
    49  		return nil, err
    50  	}
    51  	// checksum is ISO13616, Ethereum address is base-36
    52  	l := iban.currencyLen + 2 + iban.orgCodeLen
    53  	// bigAddr, _ := new(big.Int).SetString(iban.iban[l:], 36)
    54  	// hex := hexutil.EncodeBig(bigAddr)
    55  	bytes := base36.DecodeToBytes(true, iban.iban[l:])
    56  	return &Customer{
    57  		currency:  iban.iban[:iban.currencyLen],
    58  		orgCode:   iban.iban[iban.currencyLen+2 : iban.currencyLen+2+iban.orgCodeLen],
    59  		resultLen: len(bytes) + 2,
    60  		customer:  bytes,
    61  	}, nil
    62  }
    63  
    64  //export ConvertAddressToICAP
    65  func ConvertAddressToICAP(prefix string, orgCode string, a types.Address) string {
    66  	prefix = strings.ToUpper(prefix)
    67  	enc := base36.EncodeBytes(a.Bytes())
    68  	// zero padd encoded address to Direct ICAP length if needed
    69  	if len(enc) < 31 {
    70  		enc = join(strings.Repeat("0", 31-len(enc)), enc)
    71  	}
    72  	icap := join(prefix, checkDigits(prefix, orgCode, enc), orgCode, enc)
    73  
    74  	l := 31 + len(orgCode) + len(prefix) + 2
    75  	if len(icap) != l {
    76  		log.Println("生成的地址出错", "addr", a.Hex())
    77  	}
    78  	return strings.ToLower(icap)
    79  }
    80  
    81  //export ConvertICAPToAddress
    82  func ConvertICAPToAddress(prefix string, orgCodeLen int, s string) (types.Address, error) {
    83  	prefix = strings.ToUpper(prefix)
    84  	s = strings.ToUpper(s)
    85  	l := 31 + orgCodeLen + len(prefix) + 2
    86  	switch len(s) {
    87  	case 35: // "XE" + 2 digit checksum + 31 base-36 chars of address
    88  		return parseICAP(prefix, s)
    89  	case 34: // "XE" + 2 digit checksum + 30 base-36 chars of address
    90  		return parseICAP(prefix, s)
    91  	case 20: // "XE" + 2 digit checksum + 3-char asset identifier +
    92  		// 4-char institution identifier + 9-char institution client identifier
    93  		return parseIndirectICAP(prefix, s)
    94  	case l: // "prefix" + 2 digit checksum + orgCodeLen + 3-char asset identifier +
    95  		// 4-char institution identifier + 9-char institution client identifier
    96  		return parseSelfICAP(prefix, orgCodeLen, s)
    97  	default:
    98  		return types.Address{}, errICAPLength
    99  	}
   100  }
   101  func parseSelfICAP(prefix string, orgCodeLen int, s string) (types.Address, error) {
   102  	if !strings.HasPrefix(s, prefix) {
   103  		return types.Address{}, errICAPCountryCode
   104  	}
   105  	if orgCodeLen > 0 {
   106  		if err := ValidCheckSumWithLen(len(prefix), orgCodeLen, s); err != nil {
   107  			return types.Address{}, err
   108  		}
   109  	} else {
   110  		if err := validCheckSum(s); err != nil {
   111  			return types.Address{}, err
   112  		}
   113  	}
   114  	// checksum is ISO13616, Ethereum address is base-36
   115  	l := len(prefix) + 2 + orgCodeLen
   116  	bigAddr, _ := new(big.Int).SetString(s[l:], 36)
   117  	return types.BigToAddress(bigAddr), nil
   118  }
   119  func parseICAP(prefix string, s string) (types.Address, error) {
   120  	if !strings.HasPrefix(s, prefix) {
   121  		return types.Address{}, errICAPCountryCode
   122  	}
   123  	if err := validCheckSum(s); err != nil {
   124  		return types.Address{}, err
   125  	}
   126  	// checksum is ISO13616, Ethereum address is base-36
   127  	bigAddr, _ := new(big.Int).SetString(s[4:], 36)
   128  	return types.BigToAddress(bigAddr), nil
   129  }
   130  func parseIndirectICAP(prefix string, s string) (types.Address, error) {
   131  	if !strings.HasPrefix(s, prefix) {
   132  		return types.Address{}, errICAPCountryCode
   133  	}
   134  	if s[4:7] != "ETH" {
   135  		return types.Address{}, errICAPAssetIdent
   136  	}
   137  	if err := validCheckSum(s); err != nil {
   138  		return types.Address{}, err
   139  	}
   140  	return types.Address{}, errors.New("not implemented")
   141  }
   142  
   143  // https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
   144  func validCheckSum(s string) error {
   145  	s = join(s[4:], s[:4])
   146  	expanded, err := iso13616Expand(s)
   147  	if err != nil {
   148  		return err
   149  	}
   150  	checkSumNum, _ := new(big.Int).SetString(expanded, 10)
   151  	if checkSumNum.Mod(checkSumNum, Big97).Cmp(Big1) != 0 {
   152  		return errICAPChecksum
   153  	}
   154  	return nil
   155  }
   156  
   157  func ValidCheckSumWithLen(prefixLen, orgCodeLen int, s string) error {
   158  	// s=prefix+check+orgCode+addr
   159  	l := prefixLen + 2
   160  	s1 := join(s[l:], s[:prefixLen], s[prefixLen:prefixLen+2]) // orgCode+addr+prefix+check
   161  	expanded, err := iso13616Expand(s1)
   162  	if err != nil {
   163  		return err
   164  	}
   165  	checkSumNum, _ := new(big.Int).SetString(expanded, 10)
   166  	if checkSumNum.Mod(checkSumNum, Big97).Cmp(Big1) != 0 {
   167  		return errICAPChecksum
   168  	}
   169  	return nil
   170  }
   171  
   172  func checkDigits(prefix, orgCode, s string) string {
   173  	prefix = strings.ToUpper(prefix)
   174  	expanded, _ := iso13616Expand(join(orgCode, s, prefix+"00")) // orgCode+addr+prefix+00
   175  	num, _ := new(big.Int).SetString(expanded, 10)
   176  	num.Sub(Big98, num.Mod(num, Big97))
   177  
   178  	checkDigits := num.String()
   179  	// zero padd checksum
   180  	if len(checkDigits) == 1 {
   181  		checkDigits = join("0", checkDigits)
   182  	}
   183  	return checkDigits
   184  }
   185  
   186  // not base-36, but expansion to decimal literal: A = 10, B = 11, ... Z = 35
   187  func iso13616Expand(s string) (string, error) {
   188  	var parts []string
   189  	for _, c := range s {
   190  		i := uint64(c)
   191  		// 0-9 or A-Z
   192  		if i < 48 || (i > 57 && i < 65) || i > 90 {
   193  			return "", errICAPEncoding
   194  		}
   195  
   196  		if i > 97 {
   197  			parts = append(parts, strconv.FormatUint(uint64(c)-87, 10))
   198  		} else if i >= 65 {
   199  			parts = append(parts, strconv.FormatUint(uint64(c)-55, 10))
   200  		} else {
   201  			parts = append(parts, string(c))
   202  		}
   203  	}
   204  	return join(parts...), nil
   205  }
   206  
   207  func join(s ...string) string {
   208  	return strings.Join(s, "")
   209  }
   210  
   211  func LeftJoin(str string, resultLen int) string {
   212  	if len(str) < resultLen {
   213  		str = join(strings.Repeat("0", resultLen-len(str)), str)
   214  	}
   215  	return str
   216  }