icap.go 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. // Copyright 2015 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. // Spec at https://github.com/ethereum/wiki/wiki/ICAP:-Inter-exchange-Client-Address-Protocol
  17. package common
  18. import (
  19. "errors"
  20. "math/big"
  21. "strconv"
  22. "strings"
  23. )
  24. var (
  25. Base36Chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
  26. ICAPLengthError = errors.New("Invalid ICAP length")
  27. ICAPEncodingError = errors.New("Invalid ICAP encoding")
  28. ICAPChecksumError = errors.New("Invalid ICAP checksum")
  29. ICAPCountryCodeError = errors.New("Invalid ICAP country code")
  30. ICAPAssetIdentError = errors.New("Invalid ICAP asset identifier")
  31. ICAPInstCodeError = errors.New("Invalid ICAP institution code")
  32. ICAPClientIdentError = errors.New("Invalid ICAP client identifier")
  33. )
  34. func ICAPToAddress(s string) (Address, error) {
  35. switch len(s) {
  36. case 35: // "XE" + 2 digit checksum + 31 base-36 chars of address
  37. return parseICAP(s)
  38. case 34: // "XE" + 2 digit checksum + 30 base-36 chars of address
  39. return parseICAP(s)
  40. case 20: // "XE" + 2 digit checksum + 3-char asset identifier +
  41. // 4-char institution identifier + 9-char institution client identifier
  42. return parseIndirectICAP(s)
  43. default:
  44. return Address{}, ICAPLengthError
  45. }
  46. }
  47. func parseICAP(s string) (Address, error) {
  48. if !strings.HasPrefix(s, "XE") {
  49. return Address{}, ICAPCountryCodeError
  50. }
  51. if err := validCheckSum(s); err != nil {
  52. return Address{}, err
  53. }
  54. // checksum is ISO13616, Ethereum address is base-36
  55. bigAddr, _ := new(big.Int).SetString(s[4:], 36)
  56. return BigToAddress(bigAddr), nil
  57. }
  58. func parseIndirectICAP(s string) (Address, error) {
  59. if !strings.HasPrefix(s, "XE") {
  60. return Address{}, ICAPCountryCodeError
  61. }
  62. if s[4:7] != "ETH" {
  63. return Address{}, ICAPAssetIdentError
  64. }
  65. if err := validCheckSum(s); err != nil {
  66. return Address{}, err
  67. }
  68. // TODO: integrate with ICAP namereg
  69. return Address{}, errors.New("not implemented")
  70. }
  71. func AddressToICAP(a Address) (string, error) {
  72. enc := base36Encode(a.Big())
  73. // zero padd encoded address to Direct ICAP length if needed
  74. if len(enc) < 30 {
  75. enc = join(strings.Repeat("0", 30-len(enc)), enc)
  76. }
  77. icap := join("XE", checkDigits(enc), enc)
  78. return icap, nil
  79. }
  80. // TODO: integrate with ICAP namereg when it's available
  81. func AddressToIndirectICAP(a Address, instCode string) (string, error) {
  82. // return addressToIndirectICAP(a, instCode)
  83. return "", errors.New("not implemented")
  84. }
  85. func addressToIndirectICAP(a Address, instCode string) (string, error) {
  86. // TODO: add addressToClientIdent which grabs client ident from ICAP namereg
  87. //clientIdent := addressToClientIdent(a)
  88. clientIdent := "todo"
  89. return clientIdentToIndirectICAP(instCode, clientIdent)
  90. }
  91. func clientIdentToIndirectICAP(instCode, clientIdent string) (string, error) {
  92. if len(instCode) != 4 || !validBase36(instCode) {
  93. return "", ICAPInstCodeError
  94. }
  95. if len(clientIdent) != 9 || !validBase36(instCode) {
  96. return "", ICAPClientIdentError
  97. }
  98. // currently ETH is only valid asset identifier
  99. s := join("ETH", instCode, clientIdent)
  100. return join("XE", checkDigits(s), s), nil
  101. }
  102. // https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
  103. func validCheckSum(s string) error {
  104. s = join(s[4:], s[:4])
  105. expanded, err := iso13616Expand(s)
  106. if err != nil {
  107. return err
  108. }
  109. checkSumNum, _ := new(big.Int).SetString(expanded, 10)
  110. if checkSumNum.Mod(checkSumNum, Big97).Cmp(Big1) != 0 {
  111. return ICAPChecksumError
  112. }
  113. return nil
  114. }
  115. func checkDigits(s string) string {
  116. expanded, _ := iso13616Expand(strings.Join([]string{s, "XE00"}, ""))
  117. num, _ := new(big.Int).SetString(expanded, 10)
  118. num.Sub(Big98, num.Mod(num, Big97))
  119. checkDigits := num.String()
  120. // zero padd checksum
  121. if len(checkDigits) == 1 {
  122. checkDigits = join("0", checkDigits)
  123. }
  124. return checkDigits
  125. }
  126. // not base-36, but expansion to decimal literal: A = 10, B = 11, ... Z = 35
  127. func iso13616Expand(s string) (string, error) {
  128. var parts []string
  129. if !validBase36(s) {
  130. return "", ICAPEncodingError
  131. }
  132. for _, c := range s {
  133. i := uint64(c)
  134. if i >= 65 {
  135. parts = append(parts, strconv.FormatUint(uint64(c)-55, 10))
  136. } else {
  137. parts = append(parts, string(c))
  138. }
  139. }
  140. return join(parts...), nil
  141. }
  142. func base36Encode(i *big.Int) string {
  143. var chars []rune
  144. x := new(big.Int)
  145. for {
  146. x.Mod(i, Big36)
  147. chars = append(chars, rune(Base36Chars[x.Uint64()]))
  148. i.Div(i, Big36)
  149. if i.Cmp(Big0) == 0 {
  150. break
  151. }
  152. }
  153. // reverse slice
  154. for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
  155. chars[i], chars[j] = chars[j], chars[i]
  156. }
  157. return string(chars)
  158. }
  159. func validBase36(s string) bool {
  160. for _, c := range s {
  161. i := uint64(c)
  162. // 0-9 or A-Z
  163. if i < 48 || (i > 57 && i < 65) || i > 90 {
  164. return false
  165. }
  166. }
  167. return true
  168. }
  169. func join(s ...string) string {
  170. return strings.Join(s, "")
  171. }