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.
- checksums.yaml +7 -0
- data/bin/ton-sdk-ruby +7 -0
- data/lib/ton-sdk-ruby/bit_array/bit_array.rb +169 -0
- data/lib/ton-sdk-ruby/boc/builder.rb +261 -0
- data/lib/ton-sdk-ruby/boc/cell.rb +362 -0
- data/lib/ton-sdk-ruby/boc/hashmap.rb +398 -0
- data/lib/ton-sdk-ruby/boc/mask.rb +46 -0
- data/lib/ton-sdk-ruby/boc/serializer.rb +428 -0
- data/lib/ton-sdk-ruby/boc/slice.rb +335 -0
- data/lib/ton-sdk-ruby/johnny_mnemonic/ton_mnemonic.rb +144 -0
- data/lib/ton-sdk-ruby/johnny_mnemonic/utils.rb +40 -0
- data/lib/ton-sdk-ruby/johnny_mnemonic/words/english.json +2050 -0
- data/lib/ton-sdk-ruby/providers/provider.rb +41 -0
- data/lib/ton-sdk-ruby/providers/toncenter.rb +71 -0
- data/lib/ton-sdk-ruby/types/address.rb +203 -0
- data/lib/ton-sdk-ruby/types/block.rb +388 -0
- data/lib/ton-sdk-ruby/types/coins.rb +188 -0
- data/lib/ton-sdk-ruby/utils/bits.rb +25 -0
- data/lib/ton-sdk-ruby/utils/checksum.rb +46 -0
- data/lib/ton-sdk-ruby/utils/hash.rb +15 -0
- data/lib/ton-sdk-ruby/utils/helpers.rb +161 -0
- data/lib/ton-sdk-ruby/utils/numbers.rb +42 -0
- data/lib/ton-sdk-ruby/version.rb +4 -0
- data/lib/ton-sdk-ruby.rb +29 -0
- metadata +137 -0
@@ -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
|