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,362 @@
1
+ module TonSdkRuby
2
+
3
+ HASH_BITS = 256
4
+ DEPTH_BITS = 16
5
+
6
+ module CellType
7
+ Ordinary = -1
8
+ PrunedBranch = 1
9
+ LibraryReference = 2
10
+ MerkleProof = 3
11
+ MerkleUpdate = 4
12
+ end
13
+
14
+ def validate_ordinary(bits, refs)
15
+ if bits.length > 1023
16
+ raise "Ordinary cell can't have more than 1023 bits, got #{bits.length}"
17
+ end
18
+
19
+ if refs.length > 4
20
+ raise "Ordinary cell can't have more than 4 refs, got #{refs.length}"
21
+ end
22
+ end
23
+
24
+ def validate_pruned_branch(bits, refs)
25
+ min_size = 8 + 8 + (1 * (HASH_BITS + DEPTH_BITS))
26
+
27
+ if bits.length < min_size
28
+ raise "Pruned Branch cell can't have less than (8 + 8 + 256 + 16) bits, got #{bits.length}"
29
+ end
30
+
31
+ if refs.length != 0
32
+ raise "Pruned Branch cell can't have refs, got #{refs.length}"
33
+ end
34
+
35
+ type = bits_to_int_uint(bits[0...8], { type: 'int' })
36
+
37
+ if type != CellType::PrunedBranch
38
+ raise "Pruned Branch cell type must be exactly #{CellType::PrunedBranch}, got #{type}"
39
+ end
40
+
41
+ mask = Mask.new(bits_to_int_uint(bits[8...16], { type: 'uint' }))
42
+
43
+ if mask.level < 1 || mask.level > 3
44
+ raise "Pruned Branch cell level must be >= 1 and <= 3, got #{mask.level}"
45
+ end
46
+
47
+ hash_count = mask.apply(mask.level - 1)[:hashCount]
48
+ size = 8 + 8 + (hash_count * (HASH_BITS + DEPTH_BITS))
49
+
50
+ if bits.length != size
51
+ raise "Pruned Branch cell with level #{mask.level} must have exactly #{size} bits, got #{bits.length}"
52
+ end
53
+ end
54
+
55
+ def validate_library_reference(bits, refs)
56
+ # Type + hash
57
+ size = 8 + HASH_BITS
58
+
59
+ if bits.length != size
60
+ raise "Library Reference cell must have exactly (8 + 256) bits, got \"#{bits.length}\""
61
+ end
62
+
63
+ if refs.length != 0
64
+ raise "Library Reference cell can't have refs, got \"#{refs.length}\""
65
+ end
66
+
67
+ type = bits_to_int_uint(bits[0..7], { type: 'int' })
68
+
69
+ if type != CellType::LibraryReference
70
+ raise "Library Reference cell type must be exactly #{CellType::LibraryReference}, got \"#{type}\""
71
+ end
72
+ end
73
+
74
+ def validate_merkle_proof(bits, refs)
75
+ # Type + hash + depth
76
+ size = 8 + HASH_BITS + DEPTH_BITS
77
+
78
+ if bits.length != size
79
+ raise "Merkle Proof cell must have exactly (8 + 256 + 16) bits, got \"#{bits.length}\""
80
+ end
81
+
82
+ if refs.length != 1
83
+ raise "Merkle Proof cell must have exactly 1 ref, got \"#{refs.length}\""
84
+ end
85
+
86
+ type = bits_to_int_uint(bits[0..7], { type: 'int' })
87
+
88
+ if type != CellType::MerkleProof
89
+ raise "Merkle Proof cell type must be exactly #{CellType::MerkleProof}, got \"#{type}\""
90
+ end
91
+
92
+ data = bits[8..-1]
93
+ proof_hash = bits_to_hex(data[0..(HASH_BITS - 1)])
94
+ proof_depth = bits_to_int_uint(data[HASH_BITS..(HASH_BITS + DEPTH_BITS - 1)], { type: 'uint' })
95
+ ref_hash = refs[0].hash(0)
96
+ ref_depth = refs[0].depth(0)
97
+
98
+ if proof_hash != ref_hash
99
+ raise "Merkle Proof cell ref hash must be exactly \"#{proof_hash}\", got \"#{ref_hash}\""
100
+ end
101
+
102
+ if proof_depth != ref_depth
103
+ raise "Merkle Proof cell ref depth must be exactly \"#{proof_depth}\", got \"#{ref_depth}\""
104
+ end
105
+ end
106
+
107
+ def validate_merkle_update(bits, refs)
108
+ size = 8 + (2 * (256 + 16))
109
+
110
+ if bits.length != size
111
+ raise "Merkle Update cell must have exactly (8 + (2 * (256 + 16))) bits, got #{bits.length}"
112
+ end
113
+
114
+ if refs.length != 2
115
+ raise "Merkle Update cell must have exactly 2 refs, got #{refs.length}"
116
+ end
117
+
118
+ type = bits_to_int_uint(bits[0..7], { type: 'int' })
119
+
120
+ if type != CellType::MerkleUpdate
121
+ raise "Merkle Update cell type must be exactly #{CellType::MerkleUpdate}, got #{type}"
122
+ end
123
+
124
+ data = bits[8..-1]
125
+ hashes = [data[0..255], data[256..511]].map { |el| bits_to_hex(el) }
126
+ depths = [data[512..527], data[528..543]].map { |el| bits_to_int_uint(el, { type: 'uint' }) }
127
+
128
+ refs.each_with_index do |ref, i|
129
+ proof_hash = hashes[i]
130
+ proof_depth = depths[i]
131
+ ref_hash = ref.hash(0)
132
+ ref_depth = ref.depth(0)
133
+
134
+ if proof_hash != ref_hash
135
+ raise "Merkle Update cell ref ##{i} hash must be exactly '#{proof_hash}', got '#{ref_hash}'"
136
+ end
137
+
138
+ if proof_depth != ref_depth
139
+ raise "Merkle Update cell ref ##{i} depth must be exactly '#{proof_depth}', got '#{ref_depth}'"
140
+ end
141
+ end
142
+ end
143
+
144
+ def get_mapper(type)
145
+ map = {
146
+ CellType::Ordinary => {
147
+ validate: method(:validate_ordinary),
148
+ mask: -> (_b, r) { Mask.new(r.reduce(0) { |acc, el| acc | el.mask.value }) }
149
+ },
150
+ CellType::PrunedBranch => {
151
+ validate: method(:validate_pruned_branch),
152
+ mask: lambda { |b| Mask.new(bits_to_int_uint(b[8..15], { type: 'uint' })) }
153
+ },
154
+ CellType::LibraryReference => {
155
+ validate: method(:validate_library_reference),
156
+ mask: -> { Mask.new(0) }
157
+ },
158
+ CellType::MerkleProof => {
159
+ validate: method(:validate_merkle_proof),
160
+ mask: lambda { |_b, r| Mask.new(r[0].mask.value >> 1) }
161
+ },
162
+ CellType::MerkleUpdate => {
163
+ validate: method(:validate_merkle_update),
164
+ mask: lambda { |_b, r| Mask.new((r[0].mask.value | r[1].mask.value) >> 1) }
165
+ }
166
+ }
167
+
168
+ result = map[type]
169
+
170
+ if result.nil?
171
+ raise 'Unknown cell type'
172
+ end
173
+
174
+ result
175
+ end
176
+
177
+
178
+ class Cell
179
+ attr_accessor :bits, :refs, :type, :mask
180
+ attr_reader :hashes, :depths
181
+ private :bits, :refs, :type, :mask, :hashes, :depths
182
+
183
+ def initialize(options = {})
184
+ options = { bits: [], refs: [], type: CellType::Ordinary }.merge(options)
185
+
186
+ mapper = get_mapper(options[:type])
187
+ validate = mapper[:validate]
188
+ mask = mapper[:mask]
189
+
190
+ validate.call(options[:bits], options[:refs])
191
+ @mask = mask.call(options[:bits], options[:refs])
192
+ @type = options[:type]
193
+ @bits = options[:bits]
194
+ @refs = options[:refs]
195
+ @depths = {}
196
+ @hashes = {}
197
+
198
+ init()
199
+ end
200
+
201
+ # Get current Cell instance bits
202
+ def bits
203
+ @bits.dup
204
+ end
205
+
206
+ # Get current Cell instance refs
207
+ def refs
208
+ @refs.dup
209
+ end
210
+
211
+ # Get current Cell instance Mask (that includes level, hashes count, etc...)
212
+ def mask
213
+ @mask
214
+ end
215
+
216
+ # Get current Cell instance CellType
217
+ def type
218
+ @type
219
+ end
220
+
221
+ # Check if current Cell instance is exotic type
222
+ def exotic
223
+ @type != CellType::Ordinary
224
+ end
225
+
226
+ # Calculate depth descriptor
227
+ def self.get_depth_descriptor(depth)
228
+ descriptor = [(depth / 256).floor, depth % 256].pack('C*')
229
+ bytes_to_bits(descriptor)
230
+ end
231
+
232
+ # Get current Cell instance refs descriptor
233
+ def get_refs_descriptor(mask = nil)
234
+ value = @refs.length +
235
+ (exotic ? 8 : 0) +
236
+ ((mask ? mask.value : @mask.value) * 32)
237
+
238
+ descriptor = [value].pack('C')
239
+ bytes_to_bits(descriptor)
240
+ end
241
+
242
+ # Get current Cell instance bits descriptor
243
+ def get_bits_descriptor
244
+ value = (@bits.length / 8.0).ceil + (@bits.length / 8.0).floor
245
+ descriptor = [value].pack('C')
246
+ bytes_to_bits(descriptor)
247
+ end
248
+
249
+ # Get current Cell instance augmented bits
250
+ def get_augmented_bits
251
+ augment(@bits)
252
+ end
253
+
254
+ # Get cell's hash in hex (max level by default)
255
+ def hash(level = 3)
256
+ return @hashes[@mask.apply(level).hash_index] if @type != CellType::PrunedBranch
257
+
258
+ hash_index = @mask.apply(level).hash_index
259
+ this_hash_index = @mask.hash_index
260
+ skip = 16 + hash_index * HASH_BITS
261
+
262
+ if hash_index != this_hash_index
263
+ bits_to_hex(@bits[skip...(skip + HASH_BITS)])
264
+ else
265
+ @hashes[0]
266
+ end
267
+ end
268
+
269
+ # Get cell's depth (max level by default)
270
+ def depth(level = 3)
271
+ return @depths[@mask.apply(level).hash_index] if @type != CellType::PrunedBranch
272
+
273
+ hash_index = @mask.apply(level).hash_index
274
+ this_hash_index = @mask.hash_index
275
+ skip = 16 + this_hash_index * HASH_BITS + hash_index * DEPTH_BITS
276
+
277
+ if hash_index != this_hash_index
278
+ bits_to_int_uint(@bits[skip...(skip + DEPTH_BITS)], type: 'uint')
279
+ else
280
+ @depths[0]
281
+ end
282
+ end
283
+
284
+ # Get Slice from current instance
285
+ def parse
286
+ Slice.parse(self)
287
+ end
288
+
289
+ # Print cell as fift-hex
290
+ def print(indent = 1, size = 0)
291
+ # TODO: fix this logic
292
+
293
+ bits = @bits.dup
294
+ are_divisible = bits.length % 4 == 0
295
+ augmented = are_divisible ? bits : augment(bits, 4)
296
+ fift_hex = "#{bits_to_hex(augmented).upcase}#{are_divisible ? '' : '_'}"
297
+ output = ["#{' ' * (indent * size)}x{#{fift_hex}}\n"]
298
+
299
+ @refs.each do |ref|
300
+ output.push(ref.print(indent, size + 1))
301
+ end
302
+
303
+ output.join('')
304
+ end
305
+
306
+ # Checks Cell equality by comparing cell hashes
307
+ def eq(cell)
308
+ hash == cell.hash
309
+ end
310
+
311
+
312
+ private
313
+
314
+ def init
315
+ has_refs = @refs.length.positive?
316
+ is_merkle = [CellType::MerkleProof, CellType::MerkleUpdate].include?(@type)
317
+ is_pruned_branch = @type == CellType::PrunedBranch
318
+ hash_index_offset = is_pruned_branch ? @mask.hash_count - 1 : 0
319
+
320
+ hash_index = 0
321
+ (0..@mask.level).each do |level_index|
322
+ next unless @mask.is_significant(level_index)
323
+ next if hash_index < hash_index_offset
324
+
325
+ if (hash_index == hash_index_offset && level_index != 0 && !is_pruned_branch) ||
326
+ (hash_index != hash_index_offset && level_index == 0 && is_pruned_branch)
327
+ raise 'Can\'t deserialize cell'
328
+ end
329
+ ref_level = level_index + (is_merkle ? 1 : 0)
330
+ refs_descriptor = get_refs_descriptor(@mask.apply(level_index))
331
+ bits_descriptor = get_bits_descriptor
332
+ data = if hash_index != hash_index_offset
333
+ hex_to_bits(@hashes[hash_index - hash_index_offset - 1])
334
+ else
335
+ get_augmented_bits
336
+ end
337
+ depth_repr = []
338
+ hash_repr = []
339
+ depth = 0
340
+ @refs.each do |ref|
341
+ ref_depth = ref.depth(ref_level)
342
+ ref_hash = ref.hash(ref_level)
343
+
344
+ depth_repr.concat(Cell.get_depth_descriptor(ref_depth))
345
+ hash_repr.concat(hex_to_bits(ref_hash))
346
+ depth = [depth, ref_depth].max
347
+ end
348
+ representation = refs_descriptor + bits_descriptor + data + depth_repr + hash_repr
349
+
350
+ if @refs.length.positive? && depth >= 1024
351
+ raise 'Cell depth can\'t be more than 1024'
352
+ end
353
+ dest = hash_index - hash_index_offset
354
+
355
+ @depths[dest] = depth + (has_refs ? 1 : 0)
356
+ @hashes[dest] = sha256(bits_to_bytes(representation))
357
+ hash_index += 1
358
+ end
359
+ end
360
+
361
+ end
362
+ end