ton-sdk-ruby 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,335 @@
1
+ module TonSdkRuby
2
+
3
+ class Slice
4
+ def initialize(bits, refs)
5
+ @bits = bits
6
+ @refs = refs
7
+ end
8
+
9
+ def bits
10
+ Array.new(@bits)
11
+ end
12
+
13
+ def refs
14
+ Array.new(@refs)
15
+ end
16
+
17
+ def skip(size)
18
+ skip_bits(size)
19
+ end
20
+
21
+ def skip_bits(size)
22
+ if @bits.length < size
23
+ raise 'Slice: bits overflow.'
24
+ end
25
+
26
+ @bits.shift(size)
27
+ self
28
+ end
29
+
30
+ def skip_refs(size)
31
+ if @refs.length < size
32
+ raise 'Slice: refs overflow.'
33
+ end
34
+
35
+ @refs.shift(size)
36
+ self
37
+ end
38
+
39
+ def skip_dict
40
+ is_empty = load_bit == 0
41
+ return skip_refs(1) unless is_empty
42
+
43
+ self
44
+ end
45
+
46
+ def load_ref
47
+ raise 'Slice: refs overflow.' if @refs.empty?
48
+
49
+ @refs.shift
50
+ end
51
+
52
+ def preload_ref
53
+ raise 'Slice: refs overflow.' if @refs.empty?
54
+
55
+ @refs[0]
56
+ end
57
+
58
+ def load_maybe_ref
59
+ load_bit == 1 ? load_ref : nil
60
+ end
61
+
62
+ def preload_maybe_ref
63
+ preload_bit == 1 ? preload_ref : nil
64
+ end
65
+
66
+ def load_bit
67
+ raise 'Slice: bits overflow.' if @bits.empty?
68
+
69
+ @bits.shift
70
+ end
71
+
72
+ def preload_bit
73
+ raise 'Slice: bits overflow.' if @bits.empty?
74
+
75
+ @bits[0]
76
+ end
77
+
78
+ def preload_bit
79
+ raise 'Slice: bits overflow.' if @bits.empty?
80
+
81
+ @bits[0]
82
+ end
83
+
84
+ def load_bits(size)
85
+ raise 'Slice: bits overflow.' if size < 0 || @bits.length < size
86
+
87
+ @bits.shift(size)
88
+ end
89
+
90
+ def preload_bits(size)
91
+ raise 'Slice: bits overflow.' if size < 0 || @bits.length < size
92
+
93
+ @bits[0, size]
94
+ end
95
+
96
+ def load_int(size)
97
+ bits = load_bits(size)
98
+ bits_to_int_uint(bits, { type: :int })
99
+ end
100
+
101
+ def preload_int(size)
102
+ bits = preload_bits(size)
103
+ bits_to_int_uint(bits, { type: :int })
104
+ end
105
+
106
+ def load_big_int(size)
107
+ bits = load_bits(size)
108
+ bits_to_big_int(bits)[:value]
109
+ end
110
+
111
+ def preload_big_int(size)
112
+ bits = preload_bits(size)
113
+ bits_to_big_int(bits)[:value]
114
+ end
115
+
116
+ def load_uint(size)
117
+ bits = load_bits(size)
118
+
119
+ bits_to_int_uint(bits, { type: :uint })
120
+ end
121
+
122
+ def preload_uint(size)
123
+ bits = preload_bits(size)
124
+ bits_to_int_uint(bits, { type: :uint })
125
+ end
126
+
127
+ def load_big_uint(size)
128
+ bits = load_bits(size)
129
+ bits_to_big_uint(bits)[:value]
130
+ end
131
+
132
+ def preload_big_uint(size)
133
+ bits = preload_bits(size)
134
+ bits_to_big_uint(bits)[:value]
135
+ end
136
+
137
+ def load_var_int(length)
138
+ size = Math.log2(length).ceil
139
+
140
+ size_bytes = preload_uint(size)
141
+ size_bits = size_bytes * 8
142
+
143
+ raise 'Slice: can\'t perform loadVarInt – not enough bits' if @bits.length < size_bits + size
144
+
145
+ skip(size)
146
+ load_int(size_bits)
147
+ end
148
+
149
+ def preload_var_int(length)
150
+ size = Math.log2(length).ceil
151
+ size_bytes = preload_uint(size)
152
+ size_bits = size_bytes * 8
153
+ bits = preload_bits(size + size_bits)[size..-1]
154
+
155
+ bits_to_int_uint(bits, { type: :int })
156
+ end
157
+
158
+ def load_var_big_int(length)
159
+ size = Math.log2(length).ceil
160
+
161
+ size_bytes = preload_uint(size)
162
+ size_bits = size_bytes * 8
163
+
164
+ raise 'Slice: can\'t perform loadVarBigInt – not enough bits' if @bits.length < size_bits + size
165
+
166
+ bits = load_bits(size_bits)
167
+ bits_to_big_int(bits)[:value]
168
+ end
169
+
170
+ def preload_var_big_int(length)
171
+ size = Math.log2(length).ceil
172
+ size_bytes = preload_uint(size)
173
+ size_bits = size_bytes * 8
174
+ bits = preload_bits(size + size_bits)[size..-1]
175
+ bits_to_big_int(bits)[:value]
176
+ end
177
+
178
+ def load_var_uint(length)
179
+ size = Math.log2(length).ceil
180
+
181
+ size_bytes = preload_uint(size)
182
+ size_bits = size_bytes * 8
183
+
184
+ raise 'Slice: can\'t perform loadVarUint – not enough bits' if @bits.length < size_bits + size
185
+
186
+ skip(size)
187
+ load_uint(size_bits)
188
+ end
189
+
190
+ def preload_var_uint(length)
191
+ size = Math.log2(length).ceil
192
+ size_bytes = preload_uint(size)
193
+ size_bits = size_bytes * 8
194
+ bits = preload_bits(size + size_bits)[size..-1]
195
+
196
+ bits_to_int_uint(bits, { type: :uint })
197
+ end
198
+
199
+ def load_var_big_uint(length)
200
+ size = Math.log2(length).ceil
201
+ size_bytes = preload_uint(size)
202
+ size_bits = size_bytes * 8
203
+
204
+ raise 'Slice: can\'t perform loadVarBigUint – not enough bits' if @bits.length < size_bits + size
205
+
206
+ bits = skip(size).load_bits(size_bits)
207
+ bits_to_big_uint(bits)[:value]
208
+ end
209
+
210
+ def preload_var_big_uint(length)
211
+ size = Math.log2(length).ceil
212
+ size_bytes = preload_uint(size)
213
+ size_bits = size_bytes * 8
214
+ bits = preload_bits(size + size_bits)[size..-1]
215
+ bits_to_big_uint(bits)[:value]
216
+ end
217
+
218
+ def load_bytes(size)
219
+ bits = load_bits(size * 8)
220
+ bits_to_bytes(bits)
221
+ end
222
+
223
+ def preload_bytes(size)
224
+ bits = preload_bits(size * 8)
225
+ bits_to_bytes(bits)
226
+ end
227
+
228
+ def load_string(size = nil)
229
+ bytes = size.nil? ? load_bytes(@bits.length / 8) : load_bytes(size)
230
+ bytes_to_string(bytes)
231
+ end
232
+
233
+ def preload_string(size = nil)
234
+ bytes = size.nil? ? preload_bytes(@bits.length / 8) : preload_bytes(size)
235
+ bytes_to_string(bytes)
236
+ end
237
+
238
+ def load_address
239
+ flag_address_no = [0, 0]
240
+ flag_address = [1, 0]
241
+ flag = preload_bits(2)
242
+
243
+ if flag == flag_address_no
244
+ skip(2)
245
+ Address::NONE
246
+ elsif flag == flag_address
247
+ # 2 bits flag, 1 bit anycast, 8 bits workchain, 256 bits address hash
248
+ size = 2 + 1 + 8 + 256
249
+ # Slice 2 because we don't need flag bits
250
+ bits = load_bits(size)[2..-1]
251
+
252
+ # Anycast is currently unused
253
+ _anycast = bits.shift
254
+
255
+ workchain = bits_to_int_uint(bits.shift(8), type: 'int')
256
+ hash = bits_to_hex(bits.shift(256))
257
+ raw = "#{workchain}:#{hash}"
258
+
259
+ Address.new(raw)
260
+ else
261
+ raise 'Slice: bad address flag bits.'
262
+ end
263
+ end
264
+
265
+ def preload_address
266
+ flag_address_no = [0, 0]
267
+ flag_address = [1, 0]
268
+ flag = preload_bits(2)
269
+
270
+ if flag == flag_address_no
271
+ Address::NONE
272
+ elsif flag == flag_address
273
+ # 2 bits flag, 1 bit anycast, 8 bits workchain, 256 bits address hash
274
+ size = 2 + 1 + 8 + 256
275
+ bits = preload_bits(size)[2..-1]
276
+ # Splice 2 because we don't need flag bits
277
+
278
+ # Anycast is currently unused
279
+ _anycast = bits.shift
280
+
281
+ workchain = bits_to_int_uint(bits.shift(8), { type: 'int' })
282
+ hash = bits_to_hex(bits.shift(256))
283
+ raw = "#{workchain}:#{hash}"
284
+
285
+ Address.new(raw)
286
+ else
287
+ raise 'Slice: bad address flag bits.'
288
+ end
289
+ end
290
+
291
+ def load_coins(decimals = 9)
292
+ coins = load_var_big_uint(16)
293
+ Coins.new(coins, is_nano: true, decimals: decimals)
294
+ end
295
+
296
+ def preload_coins(decimals = 9)
297
+ coins = preload_var_big_uint(16)
298
+ Coins.new(coins, is_nano: true, decimals: decimals)
299
+ end
300
+
301
+ def load_dict(key_size, options = {})
302
+ dict_constructor = load_bit
303
+ is_empty = dict_constructor.zero?
304
+
305
+ if !is_empty
306
+ HashmapE.parse(
307
+ key_size,
308
+ Slice.new([dict_constructor], [load_ref]),
309
+ options
310
+ )
311
+ else
312
+ HashmapE.new(key_size, options)
313
+ end
314
+ end
315
+
316
+ def preload_dict(key_size, options = {})
317
+ dict_constructor = preload_bit
318
+ is_empty = dict_constructor.zero?
319
+
320
+ if !is_empty
321
+ HashmapE.parse(
322
+ key_size,
323
+ Slice.new([dict_constructor], [preload_ref]),
324
+ options
325
+ )
326
+ else
327
+ HashmapE.new(key_size, options)
328
+ end
329
+ end
330
+
331
+ def self.parse(cell)
332
+ Slice.new(cell.bits.dup, cell.refs.dup)
333
+ end
334
+ end
335
+ end
@@ -0,0 +1,144 @@
1
+ require 'securerandom'
2
+ require 'digest'
3
+ require 'openssl'
4
+ require 'openssl/pkcs5'
5
+ require "ed25519"
6
+
7
+ module TonSdkRuby
8
+ class TonMnemonic
9
+ include TonSdkRuby
10
+
11
+ TON_PBKDF_ITERATIONS = 100_000
12
+ TON_KEYS_SALT = 'TON default seed'
13
+ TON_SEED_SALT = 'TON seed version'
14
+ TON_PASSWORD_SALT = 'TON fast seed version'
15
+
16
+ attr_accessor :seed, :mnemonic_array, :keys, :password, :words_count
17
+
18
+ def initialize(password = nil, words_count = 24)
19
+ @words_count = words_count
20
+ @password = password
21
+ @mnemonic_array = generate_seed(words_count, password)
22
+ @seed = mnemonic_array.join(' ')
23
+ @keys = mnemonic_to_private_key(mnemonic_array, password)
24
+ end
25
+
26
+ def self.parse(mnemonic_string, password = nil)
27
+ mnemonic = new
28
+ mnemonic_string.gsub!(/\s+/, ' ')
29
+ mnemonic.mnemonic_array = mnemonic_string.split(' ')
30
+ mnemonic.words_count = mnemonic.mnemonic_array.size
31
+ mnemonic.password = password
32
+ mnemonic.seed = mnemonic.mnemonic_array.join(' ')
33
+ mnemonic.keys = mnemonic.mnemonic_to_private_key(mnemonic.mnemonic_array, password)
34
+ mnemonic
35
+ end
36
+
37
+ def get_secure_random_number(min, max)
38
+ range = max - min
39
+ bits_needed = Math.log2(range).ceil
40
+ raise 'Range is too large' if bits_needed > 53
41
+ bytes_needed = (bits_needed / 8.0).ceil
42
+ mask = (2 ** bits_needed) - 1
43
+
44
+ loop do
45
+ res = SecureRandom.random_bytes(bytes_needed)
46
+ power = (bytes_needed - 1) * 8
47
+ number_value = 0
48
+ res.each_byte do |byte|
49
+ number_value += byte.ord * (2 ** power)
50
+ power -= 8
51
+ end
52
+ number_value = number_value & mask # Truncate
53
+ return min + number_value if number_value < range
54
+ end
55
+ end
56
+
57
+ def generate_seed(words_count = 24, password = nil)
58
+ mnemonic_array = []
59
+ while true
60
+ # Regenerate new mnemonics
61
+ mnemonic_array = generate_words_ton(words_count)
62
+ # # Check password conformance
63
+ if password && password.length > 0
64
+ next unless password_needed?(mnemonic_array)
65
+ end
66
+ # Check if basic seed correct
67
+ unless basic_seed?(mnemonic_to_entropy(mnemonic_array, password))
68
+ next
69
+ end
70
+ break
71
+ end
72
+ mnemonic_array
73
+ end
74
+
75
+ def mnemonic_to_entropy(mnemonic_array, password = nil)
76
+ mnemonic_string = mnemonic_array.join(' ')
77
+ password_string = password || ''
78
+ # OpenSSL::HMAC.digest(digest, key, data)
79
+ hmac = OpenSSL::HMAC.digest(OpenSSL::Digest.new('sha512'), mnemonic_string, password_string)
80
+ entropy = bytes_to_hex(hmac.unpack('C*'))
81
+
82
+ entropy
83
+ end
84
+
85
+ def generate_words_ton(length)
86
+ mnemonic_array = []
87
+ current_file_path = File.expand_path(File.dirname(__FILE__))
88
+ bip0039en = JSON.parse(File.read("#{current_file_path}/words/english.json"))
89
+ length.times do
90
+ index = get_secure_random_number(0, length)
91
+ mnemonic_array.push(bip0039en[index])
92
+ end
93
+
94
+ mnemonic_array
95
+ end
96
+
97
+ def mnemonic_to_private_key(mnemonic_array, password = nil)
98
+ mnemonic_array = normalize_mnemonic(mnemonic_array)
99
+ seed = mnemonic_to_seed(mnemonic_array, TON_KEYS_SALT, password)
100
+ key_pair = Ed25519::SigningKey.new(seed[0, 32])
101
+ {
102
+ public: key_pair.verify_key.to_bytes.unpack1('H*'),
103
+ secret: key_pair.to_bytes.unpack1('H*')
104
+ }
105
+ end
106
+
107
+ def mnemonic_to_seed(mnemonic_array, salt, password)
108
+ entropy_hex = mnemonic_to_entropy(mnemonic_array, password)
109
+ entropy = bytes_to_data_string(hex_to_bytes(entropy_hex))
110
+ hash = OpenSSL::Digest::SHA512.new
111
+ OpenSSL::KDF.pbkdf2_hmac(entropy, salt: salt, iterations: TON_PBKDF_ITERATIONS, length: 64, hash: hash)
112
+ end
113
+
114
+ private
115
+ def basic_seed?(entropy_hex)
116
+ # pbkdf2_hmac(pass, salt, iter, keylen, digest)
117
+ entropy = bytes_to_data_string(hex_to_bytes(entropy_hex))
118
+ iter = [1, TON_PBKDF_ITERATIONS / 256].max
119
+ hash = OpenSSL::Digest::SHA512.new
120
+ key = OpenSSL::KDF.pbkdf2_hmac(entropy, salt: TON_SEED_SALT, iterations: iter, length: 64, hash: hash)
121
+
122
+ key.bytes.first == 0
123
+ end
124
+
125
+ def password_seed?(entropy_hex)
126
+ # pbkdf2_hmac(pass, salt, iter, keylen, digest)
127
+ entropy = bytes_to_data_string(hex_to_bytes(entropy_hex))
128
+ iter = 1
129
+ hash = OpenSSL::Digest::SHA512.new
130
+ key = OpenSSL::KDF.pbkdf2_hmac(entropy, salt: TON_PASSWORD_SALT, iterations: iter, length: 64, hash: hash)
131
+
132
+ key.bytes.first == 1
133
+ end
134
+
135
+ def password_needed?(mnemonic_array)
136
+ passless_entropy = mnemonic_to_entropy(mnemonic_array)
137
+ password_seed?(passless_entropy) && !basic_seed?(passless_entropy)
138
+ end
139
+
140
+ def normalize_mnemonic(words)
141
+ words.map { |v| v.downcase.strip }
142
+ end
143
+ end
144
+ end
@@ -0,0 +1,40 @@
1
+ require_relative './utils'
2
+ require 'securerandom'
3
+ require 'json'
4
+ require 'openssl'
5
+ require 'openssl/pkcs5'
6
+
7
+ module TonSdkRuby
8
+
9
+ private def derive_checksum_bits_bip39(entropy_bytes)
10
+ cs = (entropy_bytes.size * 8) / 32
11
+ hex = sha256(entropy_bytes)
12
+ bits = hex_to_bits(hex)
13
+
14
+
15
+ bits.slice(0, cs)
16
+ end
17
+
18
+ def generate_words_bip39(length)
19
+ current_file_path = File.expand_path(File.dirname(__FILE__))
20
+ bip0039en = JSON.parse(File.read("#{current_file_path}/words/english.json"))
21
+ entropy = SecureRandom.random_bytes(bytes_needed_for_words(length)).unpack('C*')
22
+ checksum_bits = derive_checksum_bits(entropy)
23
+ entropy_bits = bytes_to_bits(entropy)
24
+ full_bits = entropy_bits + checksum_bits
25
+ chunks = full_bits.join('').scan(/.{1,11}/)
26
+ words = chunks.map do |chunk|
27
+ index = chunk.to_i(2)
28
+ bip0039en[index]
29
+ end
30
+
31
+ words
32
+ end
33
+
34
+ def bytes_needed_for_words_bip39(word_count)
35
+ full_entropy = word_count * 11
36
+ checksum = full_entropy % 32
37
+ initial_entropy = full_entropy - checksum
38
+ initial_entropy / 8
39
+ end
40
+ end