mixin_bot 0.12.1 → 1.1.0

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.
Files changed (57) hide show
  1. checksums.yaml +4 -4
  2. data/lib/mixin_bot/api/address.rb +21 -0
  3. data/lib/mixin_bot/api/app.rb +5 -11
  4. data/lib/mixin_bot/api/asset.rb +9 -16
  5. data/lib/mixin_bot/api/attachment.rb +27 -22
  6. data/lib/mixin_bot/api/auth.rb +34 -56
  7. data/lib/mixin_bot/api/blaze.rb +4 -3
  8. data/lib/mixin_bot/api/conversation.rb +29 -49
  9. data/lib/mixin_bot/api/encrypted_message.rb +19 -19
  10. data/lib/mixin_bot/api/inscription.rb +71 -0
  11. data/lib/mixin_bot/api/legacy_collectible.rb +140 -0
  12. data/lib/mixin_bot/api/legacy_multisig.rb +87 -0
  13. data/lib/mixin_bot/api/legacy_output.rb +50 -0
  14. data/lib/mixin_bot/api/legacy_payment.rb +31 -0
  15. data/lib/mixin_bot/api/legacy_snapshot.rb +39 -0
  16. data/lib/mixin_bot/api/legacy_transaction.rb +173 -0
  17. data/lib/mixin_bot/api/legacy_transfer.rb +42 -0
  18. data/lib/mixin_bot/api/me.rb +13 -17
  19. data/lib/mixin_bot/api/message.rb +13 -10
  20. data/lib/mixin_bot/api/multisig.rb +17 -222
  21. data/lib/mixin_bot/api/output.rb +48 -0
  22. data/lib/mixin_bot/api/payment.rb +9 -20
  23. data/lib/mixin_bot/api/pin.rb +57 -65
  24. data/lib/mixin_bot/api/rpc.rb +12 -14
  25. data/lib/mixin_bot/api/snapshot.rb +15 -29
  26. data/lib/mixin_bot/api/tip.rb +43 -0
  27. data/lib/mixin_bot/api/transaction.rb +295 -60
  28. data/lib/mixin_bot/api/transfer.rb +69 -31
  29. data/lib/mixin_bot/api/user.rb +88 -53
  30. data/lib/mixin_bot/api/withdraw.rb +52 -53
  31. data/lib/mixin_bot/api.rb +81 -46
  32. data/lib/mixin_bot/cli/api.rb +149 -5
  33. data/lib/mixin_bot/cli/utils.rb +14 -4
  34. data/lib/mixin_bot/cli.rb +13 -10
  35. data/lib/mixin_bot/client.rb +74 -127
  36. data/lib/mixin_bot/configuration.rb +98 -0
  37. data/lib/mixin_bot/nfo.rb +174 -0
  38. data/lib/mixin_bot/transaction.rb +524 -0
  39. data/lib/mixin_bot/utils/address.rb +121 -0
  40. data/lib/mixin_bot/utils/crypto.rb +218 -0
  41. data/lib/mixin_bot/utils/decoder.rb +56 -0
  42. data/lib/mixin_bot/utils/encoder.rb +63 -0
  43. data/lib/mixin_bot/utils.rb +8 -109
  44. data/lib/mixin_bot/uuid.rb +41 -0
  45. data/lib/mixin_bot/version.rb +1 -1
  46. data/lib/mixin_bot.rb +39 -14
  47. data/lib/mvm/bridge.rb +2 -19
  48. data/lib/mvm/client.rb +11 -33
  49. data/lib/mvm/nft.rb +4 -4
  50. data/lib/mvm/registry.rb +9 -9
  51. data/lib/mvm/scan.rb +3 -5
  52. data/lib/mvm.rb +5 -6
  53. metadata +77 -103
  54. data/lib/mixin_bot/api/collectible.rb +0 -138
  55. data/lib/mixin_bot/utils/nfo.rb +0 -176
  56. data/lib/mixin_bot/utils/transaction.rb +0 -478
  57. data/lib/mixin_bot/utils/uuid.rb +0 -43
@@ -0,0 +1,524 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ class Transaction
5
+ REFERENCES_TX_VERSION = 0x04
6
+ SAFE_TX_VERSION = 0x05
7
+ DEAULT_VERSION = 5
8
+ MAGIC = [0x77, 0x77].freeze
9
+ TX_VERSION = 2
10
+ MAX_ENCODE_INT = 0xFFFF
11
+ MAX_EXTRA_SIZE = 512
12
+ NULL_BYTES = [0x00, 0x00].freeze
13
+ AGGREGATED_SIGNATURE_PREFIX = 0xFF01
14
+ AGGREGATED_SIGNATURE_ORDINAY_MASK = [0x00].freeze
15
+ AGGREGATED_SIGNATURE_SPARSE_MASK = [0x01].freeze
16
+
17
+ attr_accessor :version, :asset, :inputs, :outputs, :extra, :signatures, :aggregated, :references, :hex, :hash
18
+
19
+ def initialize(**kwargs)
20
+ @version = kwargs[:version] || DEAULT_VERSION
21
+ @asset = kwargs[:asset]
22
+ @inputs = kwargs[:inputs]
23
+ @outputs = kwargs[:outputs]
24
+ @extra = kwargs[:extra].to_s
25
+ @hex = kwargs[:hex]
26
+ @signatures = kwargs[:signatures]
27
+ @aggregated = kwargs[:aggregated]
28
+ @references = kwargs[:references]
29
+ end
30
+
31
+ def encode
32
+ raise InvalidTransactionFormatError, 'asset is required' if asset.blank?
33
+ raise InvalidTransactionFormatError, 'inputs is required' if inputs.blank?
34
+ raise InvalidTransactionFormatError, 'outputs is required' if outputs.blank?
35
+
36
+ bytes = []
37
+
38
+ # magic number
39
+ bytes += MAGIC
40
+
41
+ # version
42
+ bytes += [0, version]
43
+
44
+ # asset
45
+ bytes += [asset].pack('H*').bytes
46
+
47
+ # inputs
48
+ bytes += encode_inputs
49
+
50
+ # output
51
+ bytes += encode_outputs
52
+
53
+ # placeholder for `references`
54
+ bytes += encode_references if version >= REFERENCES_TX_VERSION
55
+
56
+ # extra
57
+ extra_bytes = extra.bytes
58
+ raise InvalidTransactionFormatError, 'extra is too long' if extra_bytes.size > MAX_EXTRA_SIZE
59
+
60
+ bytes += MixinBot.utils.encode_uint32 extra_bytes.size
61
+ bytes += extra_bytes
62
+
63
+ # aggregated
64
+ bytes += if aggregated.nil?
65
+ # signatures
66
+ encode_signatures
67
+ else
68
+ encode_aggregated_signature
69
+ end
70
+
71
+ @hash = SHA3::Digest::SHA256.hexdigest bytes.pack('C*')
72
+ @hex = bytes.pack('C*').unpack1('H*')
73
+
74
+ self
75
+ end
76
+
77
+ def decode
78
+ @bytes = [hex].pack('H*').bytes
79
+ @hash = SHA3::Digest::SHA256.hexdigest @bytes.pack('C*')
80
+
81
+ magic = @bytes.shift(2)
82
+ raise ArgumentError, 'Not valid raw' unless magic == MAGIC
83
+
84
+ _version = @bytes.shift(2)
85
+ @version = MixinBot.utils.decode_int _version
86
+
87
+ asset = @bytes.shift(32)
88
+ @asset = asset.pack('C*').unpack1('H*')
89
+
90
+ # read inputs
91
+ decode_inputs
92
+
93
+ # read outputs
94
+ decode_outputs
95
+
96
+ # read references
97
+ decode_references if version >= REFERENCES_TX_VERSION
98
+
99
+ # read extra
100
+ # unsigned 32 endian for extra size
101
+ extra_size = MixinBot.utils.decode_uint32 @bytes.shift(4)
102
+ @extra = @bytes.shift(extra_size).pack('C*')
103
+
104
+ num = MixinBot.utils.decode_uint16 @bytes.shift(2)
105
+ if num == MAX_ENCODE_INT
106
+ # aggregated
107
+ @aggregated = {}
108
+
109
+ raise ArgumentError, 'invalid aggregated' unless MixinBot.utils.decode_uint16(@bytes.shift(2)) == AGGREGATED_SIGNATURE_PREFIX
110
+
111
+ @aggregated['signature'] = @bytes.shift(64).pack('C*').unpack1('H*')
112
+
113
+ byte = @bytes.shift
114
+ case byte
115
+ when AGGREGATED_SIGNATURE_ORDINAY_MASK.first
116
+ @aggregated['signers'] = []
117
+ masks_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
118
+ masks = @bytes.shift(masks_size)
119
+ masks = Array(masks)
120
+
121
+ masks.each_with_index do |mask, i|
122
+ 8.times do |j|
123
+ k = 1 << j
124
+ aggregated['signers'].push((i * 8) + j) if mask & k == k
125
+ end
126
+ end
127
+ when AGGREGATED_SIGNATURE_SPARSE_MASK.first
128
+ signers_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
129
+ return if signers_size.zero?
130
+
131
+ aggregated['signers'] = []
132
+ signers_size.times do
133
+ aggregated['signers'].push MixinBot.utils.decode_uint16(@bytes.shift(2))
134
+ end
135
+ end
136
+ elsif num.present? && num.positive? && @bytes.size.positive?
137
+ @signatures = []
138
+ num.times do
139
+ signature = {}
140
+
141
+ keys_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
142
+
143
+ keys_size.times do
144
+ index = MixinBot.utils.decode_uint16 @bytes.shift(2)
145
+ signature[index] = @bytes.shift(64).pack('C*').unpack1('H*')
146
+ end
147
+
148
+ @signatures << signature
149
+ end
150
+ end
151
+
152
+ self
153
+ end
154
+
155
+ def to_h
156
+ {
157
+ version:,
158
+ asset:,
159
+ inputs:,
160
+ outputs:,
161
+ extra:,
162
+ signatures:,
163
+ aggregated:,
164
+ hash:,
165
+ references:
166
+ }.compact
167
+ end
168
+
169
+ private
170
+
171
+ def encode_inputs
172
+ bytes = []
173
+
174
+ bytes += MixinBot.utils.encode_uint16(inputs.size)
175
+
176
+ inputs.each do |input|
177
+ bytes += [input['hash']].pack('H*').bytes
178
+ bytes += MixinBot.utils.encode_uint16(input['index'])
179
+
180
+ # genesis
181
+ genesis = input['genesis'] || ''
182
+ if genesis.empty?
183
+ bytes += NULL_BYTES
184
+ else
185
+ genesis_bytes = [genesis].pack('H*').bytes
186
+ bytes += MixinBot.utils.encode_uint16 genesis_bytes.size
187
+ bytes += genesis_bytes
188
+ end
189
+
190
+ # deposit
191
+ deposit = input['deposit']
192
+ if deposit.nil?
193
+ bytes += NULL_BYTES
194
+ else
195
+ bytes += MAGIC
196
+ bytes += [deposit['chain']].pack('H*').bytes
197
+
198
+ asset_bytes = [deposit['asset']].pack('H*')
199
+ bytes += MixinBot.utils.encode_uint16 asset_bytes.size
200
+ bytes += asset_bytes
201
+
202
+ transaction_bytes = [deposit['transaction']].pack('H*')
203
+ bytes += MixinBot.utils.encode_uint16 transaction_bytes.size
204
+ bytes += transaction_bytes
205
+
206
+ bytes += MixinBot.utils.encode_uint64 deposit['index']
207
+
208
+ amount_bytes = MixinBot.utils.bytes_of deposit['amount']
209
+ bytes += MixinBot.utils.encode_uint16 amount_bytes.size
210
+ bytes += amount_bytes
211
+ end
212
+
213
+ # mint
214
+ mint = input['mint']
215
+ if mint.nil?
216
+ bytes += NULL_BYTES
217
+ else
218
+ bytes += MAGIC
219
+
220
+ # group
221
+ group = mint['group'] || ''
222
+ if group.empty?
223
+ bytes += MixinBot.utils.encode_uint16 NULL_BYTES
224
+ else
225
+ group_bytes = [group].pack('H*')
226
+ bytes += MixinBot.utils.encode_uint16 group_bytes.size
227
+ bytes += group_bytes
228
+ end
229
+
230
+ bytes += MixinBot.utils.encode_uint64 mint['batch']
231
+
232
+ amount_bytes = MixinBot.utils.encode_int mint['amount']
233
+ bytes += MixinBot.utils.encode_uint16 amount_bytes.size
234
+ bytes += amount_bytes
235
+ end
236
+ end
237
+
238
+ bytes
239
+ end
240
+
241
+ def encode_outputs
242
+ bytes = []
243
+
244
+ bytes += MixinBot.utils.encode_uint16 outputs.size
245
+
246
+ outputs.each do |output|
247
+ type = output['type'] || 0
248
+ bytes += [0x00, type]
249
+
250
+ # amount
251
+ amount_bytes = MixinBot.utils.encode_int (output['amount'].to_d * 1e8).round
252
+ bytes += MixinBot.utils.encode_uint16 amount_bytes.size
253
+ bytes += amount_bytes
254
+
255
+ # keys
256
+ bytes += MixinBot.utils.encode_uint16 output['keys'].size
257
+ output['keys'].each do |key|
258
+ bytes += [key].pack('H*').bytes
259
+ end
260
+
261
+ # mask
262
+ bytes += [output['mask']].pack('H*').bytes
263
+
264
+ # script
265
+ script_bytes = [output['script']].pack('H*').bytes
266
+ bytes += MixinBot.utils.encode_uint16 script_bytes.size
267
+ bytes += script_bytes
268
+
269
+ # withdrawal
270
+ withdrawal = output['withdrawal']
271
+ if withdrawal.nil?
272
+ bytes += NULL_BYTES
273
+ else
274
+ bytes += MAGIC
275
+
276
+ # chain
277
+ bytes += [withdrawal['chain']].pack('H*').bytes
278
+
279
+ # asset
280
+ @asset_bytes = [withdrawal['asset']].pack('H*')
281
+ bytes += MixinBot.utils.encode_uint16 asset_bytes.size
282
+ bytes += asset_bytes
283
+
284
+ # address
285
+ address = withdrawal['address'] || ''
286
+ if address.empty?
287
+ bytes += NULL_BYTES
288
+ else
289
+ address_bytes = [address].pack('H*').bytes
290
+ bytes += MixinBot.utils.encode_uint16 address.size
291
+ bytes += address_bytes
292
+ end
293
+
294
+ # tag
295
+ tag = withdrawal['tag'] || ''
296
+ if tag.empty?
297
+ bytes += NULL_BYTES
298
+ else
299
+ address_bytes = [tag].pack('H*').bytes
300
+ bytes += MixinBot.utils.encode_uint16 tag.size
301
+ bytes += address_bytes
302
+ end
303
+ end
304
+ end
305
+
306
+ bytes
307
+ end
308
+
309
+ def encode_references
310
+ bytes = []
311
+
312
+ bytes += MixinBot.utils.encode_uint16 references.size
313
+
314
+ references.each do |reference|
315
+ bytes += [reference].pack('H*').bytes
316
+ end
317
+
318
+ bytes
319
+ end
320
+
321
+ def encode_aggregated_signature
322
+ bytes = []
323
+
324
+ bytes += MixinBot.utils.encode_uint16 MAX_ENCODE_INT
325
+ bytes += MixinBot.utils.encode_uint16 AGGREGATED_SIGNATURE_PREFIX
326
+ bytes += [aggregated['signature']].pack('H*').bytes
327
+
328
+ signers = aggregated['signers']
329
+ if signers.empty?
330
+ bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
331
+ bytes += NULL_BYTES
332
+ else
333
+ signers.each do |sig, i|
334
+ raise ArgumentError, 'signers not sorted' if i.positive? && sig <= signers[i - 1]
335
+ raise ArgumentError, 'signers not sorted' if sig > MAX_ENCODE_INT
336
+ end
337
+
338
+ max = signers.last
339
+ if ((((max / 8) | 0) + 1) | 0) > aggregated['signature'].size * 2
340
+ bytes += AGGREGATED_SIGNATURE_SPARSE_MASK
341
+ bytes += MixinBot.utils.encode_uint16 aggregated['signers'].size
342
+ signers.map(&->(signer) { bytes += MixinBot.utils.encode_uint16(signer) })
343
+ end
344
+
345
+ masks_bytes = Array.new((max / 8) + 1, 0)
346
+ signers.each do |signer|
347
+ masks[signer / 8] = masks[signer / 8] ^ (1 << (signer % 8))
348
+ end
349
+ bytes += AGGREGATED_SIGNATURE_ORDINAY_MASK
350
+ bytes += MixinBot.utils.encode_uint16 masks_bytes.size
351
+ bytes += masks_bytes
352
+ end
353
+
354
+ bytes
355
+ end
356
+
357
+ def encode_signatures
358
+ bytes = []
359
+
360
+ sl =
361
+ if signatures.is_a? Array
362
+ signatures.size
363
+ else
364
+ 0
365
+ end
366
+
367
+ raise ArgumentError, 'signatures overflow' if sl == MAX_ENCODE_INT
368
+
369
+ bytes += MixinBot.utils.encode_uint16 sl
370
+
371
+ if sl.positive?
372
+ signatures.each do |signature|
373
+ bytes += MixinBot.utils.encode_uint16 signature.keys.size
374
+
375
+ signature.keys.sort.each do |key|
376
+ signature_bytes = [signature[key]].pack('H*').bytes
377
+ bytes += MixinBot.utils.encode_uint16 key
378
+ bytes += signature_bytes
379
+ end
380
+ end
381
+ end
382
+
383
+ bytes
384
+ end
385
+
386
+ def decode_inputs
387
+ inputs_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
388
+ @inputs = []
389
+ inputs_size.times do
390
+ input = {}
391
+ hash = @bytes.shift(32)
392
+ input['hash'] = hash.pack('C*').unpack1('H*')
393
+
394
+ index = @bytes.shift(2)
395
+ input['index'] = MixinBot.utils.decode_uint16 index
396
+
397
+ if @bytes[...2] == NULL_BYTES
398
+ @bytes.shift 2
399
+ else
400
+ genesis_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
401
+ genesis = @bytes.shift genesis_size
402
+ input['genesis'] = genesis.pack('C*').unpack1('H*')
403
+ end
404
+
405
+ if @bytes[...2] == NULL_BYTES
406
+ @bytes.shift 2
407
+ else
408
+ magic = @bytes.shift(2)
409
+ raise ArgumentError, 'Not valid input' unless magic == MAGIC
410
+
411
+ deposit = {}
412
+ deposit['chain'] = @bytes.shift(32).pack('C*').unpack1('H*')
413
+
414
+ asset_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
415
+ deposit['asset'] = @bytes.shift(asset_size).unpack1('H*')
416
+
417
+ transaction_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
418
+ deposit['transaction'] = @bytes.shift(transaction_size).unpack1('H*')
419
+
420
+ deposit['index'] = MixinBot.utils.decode_uint64 @bytes.shift(8)
421
+
422
+ amount_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
423
+ deposit['amount'] = MixinBot.utils.decode_int @bytes.shift(amount_size)
424
+
425
+ input['deposit'] = deposit
426
+ end
427
+
428
+ if @bytes[...2] == NULL_BYTES
429
+ @bytes.shift 2
430
+ else
431
+ magic = @bytes.shift(2)
432
+ raise ArgumentError, 'Not valid input' unless magic == MAGIC
433
+
434
+ mint = {}
435
+ if bytes[...2] == NULL_BYTES
436
+ @bytes.shift 2
437
+ else
438
+ group_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
439
+ mint['group'] = @bytes.shift(group_size).unpack1('H*')
440
+ end
441
+
442
+ mint['batch'] = MixinBot.utils.decode_uint64 @bytes.shift(8)
443
+ _amount_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
444
+ mint['amount'] = MixinBot.utils.decode_int bytes.shift(_amount_size)
445
+
446
+ input['mint'] = mint
447
+ end
448
+
449
+ @inputs.push input
450
+ end
451
+
452
+ self
453
+ end
454
+
455
+ def decode_outputs
456
+ outputs_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
457
+ @outputs = []
458
+ outputs_size.times do
459
+ output = {}
460
+
461
+ @bytes.shift
462
+ type = @bytes.shift
463
+ output['type'] = type
464
+
465
+ amount_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
466
+ output['amount'] = format('%.8f', MixinBot.utils.decode_int(@bytes.shift(amount_size)).to_f / 1e8).gsub(/\.?0+$/, '')
467
+
468
+ output['keys'] = []
469
+ keys_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
470
+ keys_size.times do
471
+ output['keys'].push @bytes.shift(32).pack('C*').unpack1('H*')
472
+ end
473
+
474
+ output['mask'] = @bytes.shift(32).pack('C*').unpack1('H*')
475
+
476
+ script_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
477
+ output['script'] = @bytes.shift(script_size).pack('C*').unpack1('H*')
478
+
479
+ if @bytes[...2] == NULL_BYTES
480
+ @bytes.shift 2
481
+ else
482
+ magic = @bytes.shift(2)
483
+ raise ArgumentError, 'Not valid output' unless magic == MAGIC
484
+
485
+ output['chain'] = @bytes.shift(32).pack('C*').unpack1('H*')
486
+
487
+ asset_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
488
+ output['asset'] = @bytes.shift(asset_size).unpack1('H*')
489
+
490
+ if @bytes[...2] == NULL_BYTES
491
+ @bytes.shift 2
492
+ else
493
+
494
+ adderss_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
495
+ output['adderss'] = @bytes.shift(adderss_size).pack('C*').unpack1('H*')
496
+ end
497
+
498
+ if @bytes[...2] == NULL_BYTES
499
+ @bytes.shift 2
500
+ else
501
+
502
+ tag_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
503
+ output['tag'] = @bytes.shift(tag_size).pack('C*').unpack1('H*')
504
+ end
505
+ end
506
+
507
+ @outputs.push output
508
+ end
509
+
510
+ self
511
+ end
512
+
513
+ def decode_references
514
+ references_size = MixinBot.utils.decode_uint16 @bytes.shift(2)
515
+ @references = []
516
+
517
+ references_size.times do
518
+ @references.push @bytes.shift(32).pack('C*').unpack1('H*')
519
+ end
520
+
521
+ self
522
+ end
523
+ end
524
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module MixinBot
4
+ module Utils
5
+ module Address
6
+ MAIN_ADDRESS_PREFIX = 'XIN'
7
+ MIX_ADDRESS_PREFIX = 'MIX'
8
+ MIX_ADDRESS_VERSION = 2
9
+
10
+ def build_main_address(public_key)
11
+ msg = MAIN_ADDRESS_PREFIX + public_key
12
+ checksum = SHA3::Digest::SHA256.digest msg
13
+ data = public_key + checksum[0...4]
14
+ base58 = Base58.binary_to_base58 data, :bitcoin
15
+ "#{MAIN_ADDRESS_PREFIX}#{base58}"
16
+ end
17
+
18
+ def parse_main_address(address)
19
+ raise ArgumentError, 'invalid address' unless address.start_with? MAIN_ADDRESS_PREFIX
20
+
21
+ data = address[MAIN_ADDRESS_PREFIX.length..]
22
+ data = Base58.base58_to_binary data, :bitcoin
23
+
24
+ payload = data[...-4]
25
+
26
+ msg = MAIN_ADDRESS_PREFIX + payload
27
+ checksum = SHA3::Digest::SHA256.digest msg
28
+
29
+ raise ArgumentError, 'invalid address' unless checksum[0...4] == data[-4..]
30
+
31
+ payload
32
+ end
33
+
34
+ def build_mix_address(members, threshold)
35
+ raise ArgumentError, 'members should be an array' unless members.is_a? Array
36
+ raise ArgumentError, 'members should not be empty' if members.empty?
37
+ raise ArgumentError, 'members length should less than 256' if members.length > 255
38
+
39
+ # raise ArgumentError, "invalid threshold: #{threshold}" if threshold > members.length
40
+
41
+ prefix = [MIX_ADDRESS_VERSION].pack('C*') + [threshold].pack('C*') + [members.length].pack('C*')
42
+
43
+ msg =
44
+ if members.all?(&->(member) { member.start_with? MAIN_ADDRESS_PREFIX })
45
+ members.map(&->(member) { parse_main_address(member) }).join
46
+ elsif members.none?(&->(member) { member.start_with? MAIN_ADDRESS_PREFIX })
47
+ members.map(&->(member) { MixinBot::UUID.new(hex: member).packed }).join
48
+ else
49
+ raise ArgumentError, 'invalid members'
50
+ end
51
+
52
+ checksum = SHA3::Digest::SHA256.digest(MIX_ADDRESS_PREFIX + prefix + msg)
53
+
54
+ data = prefix + msg + checksum[0...4]
55
+ data = Base58.binary_to_base58 data, :bitcoin
56
+ "#{MIX_ADDRESS_PREFIX}#{data}"
57
+ end
58
+
59
+ def parse_mix_address(address)
60
+ raise ArgumentError, 'invalid address' unless address.start_with? MIX_ADDRESS_PREFIX
61
+
62
+ data = address[MIX_ADDRESS_PREFIX.length..]
63
+ data = Base58.base58_to_binary data, :bitcoin
64
+ raise ArgumentError, 'invalid address' if data.length < 3 + 16 + 4
65
+
66
+ msg = data[...-4]
67
+ checksum = SHA3::Digest::SHA256.digest((MIX_ADDRESS_PREFIX + msg))[0...4]
68
+
69
+ raise ArgumentError, 'invalid address' unless checksum[0...4] == data[-4..]
70
+
71
+ version = data[0].ord
72
+ raise ArgumentError, 'invalid address' unless version == MIX_ADDRESS_VERSION
73
+
74
+ threshold = data[1].ord
75
+ members_count = data[2].ord
76
+
77
+ if data[3..].length == members_count * 16
78
+ members = data[3..].scan(/.{16}/)
79
+ members = members.map(&->(member) { MixinBot::UUID.new(raw: member).unpacked })
80
+ else
81
+ members = data[3..].scan(/.{64}/)
82
+ members = members.map(&->(member) { build_main_address(member) })
83
+ end
84
+
85
+ {
86
+ members:,
87
+ threshold:
88
+ }
89
+ end
90
+
91
+ def build_safe_recipient(**kwargs)
92
+ members = kwargs[:members]
93
+ threshold = kwargs[:threshold]
94
+ amount = kwargs[:amount]
95
+
96
+ members = [members] if members.is_a? String
97
+ amount = format('%.8f', amount.to_d.to_r).gsub(/\.?0+$/, '')
98
+
99
+ {
100
+ members:,
101
+ threshold:,
102
+ amount:,
103
+ mix_address: build_mix_address(members, threshold)
104
+ }
105
+ end
106
+
107
+ def burning_address
108
+ seed = "\0" * 64
109
+
110
+ digest1 = SHA3::Digest::SHA256.digest seed
111
+ digest2 = SHA3::Digest::SHA256.digest digest1
112
+ src = digest1 + digest2
113
+
114
+ spend_key = MixinBot::Utils.shared_public_key(seed)
115
+ view_key = MixinBot::Utils.shared_public_key(src)
116
+
117
+ MixinBot::Utils.build_main_address spend_key + view_key
118
+ end
119
+ end
120
+ end
121
+ end