OSDN Git Service

Merge pull request #56 from liuchengxu/log
[bytom/bytom.git] / common / icap.go
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
17 // Spec at https://github.com/ethereum/wiki/wiki/ICAP:-Inter-exchange-Client-Address-Protocol
18
19 package common
20
21 import (
22         "errors"
23         "math/big"
24         "strconv"
25         "strings"
26 )
27
28 var (
29         Base36Chars          = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
30         ICAPLengthError      = errors.New("Invalid ICAP length")
31         ICAPEncodingError    = errors.New("Invalid ICAP encoding")
32         ICAPChecksumError    = errors.New("Invalid ICAP checksum")
33         ICAPCountryCodeError = errors.New("Invalid ICAP country code")
34         ICAPAssetIdentError  = errors.New("Invalid ICAP asset identifier")
35         ICAPInstCodeError    = errors.New("Invalid ICAP institution code")
36         ICAPClientIdentError = errors.New("Invalid ICAP client identifier")
37 )
38
39 func ICAPToAddress(s string) (Address, error) {
40         switch len(s) {
41         case 35: // "XE" + 2 digit checksum + 31 base-36 chars of address
42                 return parseICAP(s)
43         case 34: // "XE" + 2 digit checksum + 30 base-36 chars of address
44                 return parseICAP(s)
45         case 20: // "XE" + 2 digit checksum + 3-char asset identifier +
46                 // 4-char institution identifier + 9-char institution client identifier
47                 return parseIndirectICAP(s)
48         default:
49                 return Address{}, ICAPLengthError
50         }
51 }
52
53 func parseICAP(s string) (Address, error) {
54         if !strings.HasPrefix(s, "XE") {
55                 return Address{}, ICAPCountryCodeError
56         }
57         if err := validCheckSum(s); err != nil {
58                 return Address{}, err
59         }
60         // checksum is ISO13616, Ethereum address is base-36
61         bigAddr, _ := new(big.Int).SetString(s[4:], 36)
62         return BigToAddress(bigAddr), nil
63 }
64
65 func parseIndirectICAP(s string) (Address, error) {
66         if !strings.HasPrefix(s, "XE") {
67                 return Address{}, ICAPCountryCodeError
68         }
69         if s[4:7] != "ETH" {
70                 return Address{}, ICAPAssetIdentError
71         }
72         if err := validCheckSum(s); err != nil {
73                 return Address{}, err
74         }
75         // TODO: integrate with ICAP namereg
76         return Address{}, errors.New("not implemented")
77 }
78
79 func AddressToICAP(a Address) (string, error) {
80         enc := base36Encode(a.Big())
81         // zero padd encoded address to Direct ICAP length if needed
82         if len(enc) < 30 {
83                 enc = join(strings.Repeat("0", 30-len(enc)), enc)
84         }
85         icap := join("XE", checkDigits(enc), enc)
86         return icap, nil
87 }
88
89 // TODO: integrate with ICAP namereg when it's available
90 func AddressToIndirectICAP(a Address, instCode string) (string, error) {
91         // return addressToIndirectICAP(a, instCode)
92         return "", errors.New("not implemented")
93 }
94
95 func addressToIndirectICAP(a Address, instCode string) (string, error) {
96         // TODO: add addressToClientIdent which grabs client ident from ICAP namereg
97         //clientIdent := addressToClientIdent(a)
98         clientIdent := "todo"
99         return clientIdentToIndirectICAP(instCode, clientIdent)
100 }
101
102 func clientIdentToIndirectICAP(instCode, clientIdent string) (string, error) {
103         if len(instCode) != 4 || !validBase36(instCode) {
104                 return "", ICAPInstCodeError
105         }
106         if len(clientIdent) != 9 || !validBase36(instCode) {
107                 return "", ICAPClientIdentError
108         }
109
110         // currently ETH is only valid asset identifier
111         s := join("ETH", instCode, clientIdent)
112         return join("XE", checkDigits(s), s), nil
113 }
114
115 // https://en.wikipedia.org/wiki/International_Bank_Account_Number#Validating_the_IBAN
116 func validCheckSum(s string) error {
117         s = join(s[4:], s[:4])
118         expanded, err := iso13616Expand(s)
119         if err != nil {
120                 return err
121         }
122         checkSumNum, _ := new(big.Int).SetString(expanded, 10)
123         if checkSumNum.Mod(checkSumNum, Big97).Cmp(Big1) != 0 {
124                 return ICAPChecksumError
125         }
126         return nil
127 }
128
129 func checkDigits(s string) string {
130         expanded, _ := iso13616Expand(strings.Join([]string{s, "XE00"}, ""))
131         num, _ := new(big.Int).SetString(expanded, 10)
132         num.Sub(Big98, num.Mod(num, Big97))
133
134         checkDigits := num.String()
135         // zero padd checksum
136         if len(checkDigits) == 1 {
137                 checkDigits = join("0", checkDigits)
138         }
139         return checkDigits
140 }
141
142 // not base-36, but expansion to decimal literal: A = 10, B = 11, ... Z = 35
143 func iso13616Expand(s string) (string, error) {
144         var parts []string
145         if !validBase36(s) {
146                 return "", ICAPEncodingError
147         }
148         for _, c := range s {
149                 i := uint64(c)
150                 if i >= 65 {
151                         parts = append(parts, strconv.FormatUint(uint64(c)-55, 10))
152                 } else {
153                         parts = append(parts, string(c))
154                 }
155         }
156         return join(parts...), nil
157 }
158
159 func base36Encode(i *big.Int) string {
160         var chars []rune
161         x := new(big.Int)
162         for {
163                 x.Mod(i, Big36)
164                 chars = append(chars, rune(Base36Chars[x.Uint64()]))
165                 i.Div(i, Big36)
166                 if i.Cmp(Big0) == 0 {
167                         break
168                 }
169         }
170         // reverse slice
171         for i, j := 0, len(chars)-1; i < j; i, j = i+1, j-1 {
172                 chars[i], chars[j] = chars[j], chars[i]
173         }
174         return string(chars)
175 }
176
177 func validBase36(s string) bool {
178         for _, c := range s {
179                 i := uint64(c)
180                 // 0-9 or A-Z
181                 if i < 48 || (i > 57 && i < 65) || i > 90 {
182                         return false
183                 }
184         }
185         return true
186 }
187
188 func join(s ...string) string {
189         return strings.Join(s, "")
190 }