1
+ // https://github.com/ton-org/ton-core/blob/main/src/utils/crc16.ts
2
+
3
+ function crc16 ( data ) {
4
+ const poly = 0x1021 ;
5
+ let reg = 0 ;
6
+ const message = Buffer . alloc ( data . length + 2 ) ;
7
+ message . set ( data ) ;
8
+ for ( let byte of message ) {
9
+ let mask = 0x80 ;
10
+ while ( mask > 0 ) {
11
+ reg <<= 1 ;
12
+ if ( byte & mask ) {
13
+ reg += 1 ;
14
+ }
15
+ mask >>= 1
16
+ if ( reg > 0xffff ) {
17
+ reg &= 0xffff ;
18
+ reg ^= poly ;
19
+ }
20
+ }
21
+ }
22
+ return Buffer . from ( [ Math . floor ( reg / 256 ) , reg % 256 ] ) ;
23
+ }
24
+ const bounceable_tag = 0x11 ;
25
+ const non_bounceable_tag = 0x51 ;
26
+ const test_flag = 0x80 ;
27
+
28
+ function parseFriendlyAddress ( src ) {
29
+ if ( typeof src === 'string' && ! Address . isFriendly ( src ) ) {
30
+ throw new Error ( 'Unknown address type' ) ;
31
+ }
32
+
33
+ const data = Buffer . isBuffer ( src ) ? src : Buffer . from ( src , 'base64' ) ;
34
+
35
+ // 1byte tag + 1byte workchain + 32 bytes hash + 2 byte crc
36
+ if ( data . length !== 36 ) {
37
+ throw new Error ( 'Unknown address type: byte length is not equal to 36' ) ;
38
+ }
39
+
40
+ // Prepare data
41
+ const addr = data . subarray ( 0 , 34 ) ;
42
+ const crc = data . subarray ( 34 , 36 ) ;
43
+ const calcedCrc = crc16 ( addr ) ;
44
+ if ( ! ( calcedCrc [ 0 ] === crc [ 0 ] && calcedCrc [ 1 ] === crc [ 1 ] ) ) {
45
+ throw new Error ( 'Invalid checksum: ' + src ) ;
46
+ }
47
+
48
+ // Parse tag
49
+ let tag = addr [ 0 ] ;
50
+ let isTestOnly = false ;
51
+ let isBounceable = false ;
52
+ if ( tag & test_flag ) {
53
+ isTestOnly = true ;
54
+ tag = tag ^ test_flag ;
55
+ }
56
+ if ( ( tag !== bounceable_tag ) && ( tag !== non_bounceable_tag ) )
57
+ throw "Unknown address tag" ;
58
+
59
+ isBounceable = tag === bounceable_tag ;
60
+
61
+ let workchain = null ;
62
+ if ( addr [ 1 ] === 0xff ) { // TODO we should read signed integer here
63
+ workchain = - 1 ;
64
+ } else {
65
+ workchain = addr [ 1 ] ;
66
+ }
67
+
68
+ const hashPart = addr . subarray ( 2 , 34 ) ;
69
+
70
+ return { isTestOnly, isBounceable, workchain, hashPart } ;
71
+ }
72
+
73
+ class Address {
74
+
75
+ static isAddress ( src ) {
76
+ return src instanceof Address ;
77
+ }
78
+
79
+ static isFriendly ( source ) {
80
+ // Check length
81
+ if ( source . length !== 48 ) {
82
+ return false ;
83
+ }
84
+ // Check if address is valid base64
85
+ if ( ! / [ A - Z a - z 0 - 9 + / _ - ] + / . test ( source ) ) {
86
+ return false ;
87
+ }
88
+
89
+ return true ;
90
+ }
91
+
92
+ static isRaw ( source ) {
93
+ // Check if has delimiter
94
+ if ( source . indexOf ( ':' ) === - 1 ) {
95
+ return false ;
96
+ }
97
+
98
+ let [ wc , hash ] = source . split ( ':' ) ;
99
+
100
+ // wc is not valid
101
+ if ( ! Number . isInteger ( parseFloat ( wc ) ) ) {
102
+ return false ;
103
+ }
104
+
105
+ // hash is not valid
106
+ if ( ! / [ a - f 0 - 9 ] + / . test ( hash . toLowerCase ( ) ) ) {
107
+ return false ;
108
+ }
109
+
110
+ // has is not correct
111
+ if ( hash . length !== 64 ) {
112
+ return false ;
113
+ }
114
+
115
+ return true ;
116
+ }
117
+
118
+ static normalize ( source ) {
119
+ if ( typeof source === 'string' ) {
120
+ return Address . parse ( source ) . toString ( ) ;
121
+ } else {
122
+ return source . toString ( ) ;
123
+ }
124
+ }
125
+
126
+ static parse ( source ) {
127
+ if ( Address . isFriendly ( source ) ) {
128
+ return this . parseFriendly ( source ) . address ;
129
+ } else if ( Address . isRaw ( source ) ) {
130
+ return this . parseRaw ( source ) ;
131
+ } else {
132
+ throw new Error ( 'Unknown address type: ' + source ) ;
133
+ }
134
+ }
135
+
136
+ static parseRaw ( source ) {
137
+ let workChain = parseInt ( source . split ( ":" ) [ 0 ] ) ;
138
+ let hash = Buffer . from ( source . split ( ":" ) [ 1 ] , 'hex' ) ;
139
+
140
+ return new Address ( workChain , hash ) ;
141
+ }
142
+
143
+ static parseFriendly ( source ) {
144
+ if ( Buffer . isBuffer ( source ) ) {
145
+ let r = parseFriendlyAddress ( source ) ;
146
+ return {
147
+ isBounceable : r . isBounceable ,
148
+ isTestOnly : r . isTestOnly ,
149
+ address : new Address ( r . workchain , r . hashPart )
150
+ } ;
151
+ } else {
152
+ let addr = source . replace ( / \- / g, '+' ) . replace ( / _ / g, '\/' ) ; // Convert from url-friendly to true base64
153
+ let r = parseFriendlyAddress ( addr ) ;
154
+ return {
155
+ isBounceable : r . isBounceable ,
156
+ isTestOnly : r . isTestOnly ,
157
+ address : new Address ( r . workchain , r . hashPart )
158
+ } ;
159
+ }
160
+ }
161
+
162
+ workChain ;
163
+ hash ;
164
+
165
+ constructor ( workChain , hash ) {
166
+ if ( hash . length !== 32 ) {
167
+ throw new Error ( 'Invalid address hash length: ' + hash . length ) ;
168
+ }
169
+
170
+ this . workChain = workChain ;
171
+ this . hash = hash ;
172
+ Object . freeze ( this ) ;
173
+ }
174
+
175
+ toRawString = ( ) => {
176
+ return this . workChain + ':' + this . hash . toString ( 'hex' ) ;
177
+ }
178
+
179
+ equals ( src ) {
180
+ if ( src . workChain !== this . workChain ) {
181
+ return false ;
182
+ }
183
+ return src . hash . equals ( this . hash ) ;
184
+ }
185
+
186
+ toRaw = ( ) => {
187
+ const addressWithChecksum = Buffer . alloc ( 36 ) ;
188
+ addressWithChecksum . set ( this . hash ) ;
189
+ addressWithChecksum . set ( [ this . workChain , this . workChain , this . workChain , this . workChain ] , 32 ) ;
190
+ return addressWithChecksum ;
191
+ }
192
+
193
+ toStringBuffer = ( args ) => {
194
+ let testOnly = ( args && args . testOnly !== undefined ) ? args . testOnly : false ;
195
+ let bounceable = ( args && args . bounceable !== undefined ) ? args . bounceable : true ;
196
+
197
+ let tag = bounceable ? bounceable_tag : non_bounceable_tag ;
198
+ if ( testOnly ) {
199
+ tag |= test_flag ;
200
+ }
201
+
202
+ const addr = Buffer . alloc ( 34 ) ;
203
+ addr [ 0 ] = tag ;
204
+ addr [ 1 ] = this . workChain ;
205
+ addr . set ( this . hash , 2 ) ;
206
+ const addressWithChecksum = Buffer . alloc ( 36 ) ;
207
+ addressWithChecksum . set ( addr ) ;
208
+ addressWithChecksum . set ( crc16 ( addr ) , 34 ) ;
209
+ return addressWithChecksum ;
210
+ }
211
+
212
+ toString = ( args ) => {
213
+ let urlSafe = ( args && args . urlSafe !== undefined ) ? args . urlSafe : true ;
214
+ let buffer = this . toStringBuffer ( args ) ;
215
+ if ( urlSafe ) {
216
+ return buffer . toString ( 'base64' ) . replace ( / \+ / g, '-' ) . replace ( / \/ / g, '_' ) ;
217
+ } else {
218
+ return buffer . toString ( 'base64' ) ;
219
+ }
220
+ }
221
+
222
+ }
223
+
224
+ function address ( src ) {
225
+ return Address . parse ( src ) ;
226
+ }
227
+
228
+ module . exports = {
229
+ address,
230
+ Address,
231
+ }
0 commit comments