iota-ruby 1.1.8-java

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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +8 -0
  3. data/.gitignore +15 -0
  4. data/.travis.yml +24 -0
  5. data/.yardopts +7 -0
  6. data/CHANGELOG.md +18 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +21 -0
  9. data/README.md +121 -0
  10. data/Rakefile +36 -0
  11. data/bin/iota-console +15 -0
  12. data/examples/multisig.rb +69 -0
  13. data/ext/ccurl/ccurl.c +134 -0
  14. data/ext/ccurl/extconf.rb +22 -0
  15. data/ext/jcurl/JCurl.java +126 -0
  16. data/ext/jcurl/JCurlService.java +36 -0
  17. data/ext/pow/ccurl-0.3.0.dll +0 -0
  18. data/ext/pow/libccurl-0.3.0.dylib +0 -0
  19. data/ext/pow/libccurl-0.3.0.so +0 -0
  20. data/iota-ruby.gemspec +37 -0
  21. data/lib/iota.rb +76 -0
  22. data/lib/iota/api/api.rb +251 -0
  23. data/lib/iota/api/commands.rb +113 -0
  24. data/lib/iota/api/transport.rb +43 -0
  25. data/lib/iota/api/wrappers.rb +429 -0
  26. data/lib/iota/crypto/bundle.rb +163 -0
  27. data/lib/iota/crypto/converter.rb +244 -0
  28. data/lib/iota/crypto/curl.rb +18 -0
  29. data/lib/iota/crypto/curl_c.rb +17 -0
  30. data/lib/iota/crypto/curl_java.rb +18 -0
  31. data/lib/iota/crypto/curl_ruby.rb +70 -0
  32. data/lib/iota/crypto/hmac.rb +27 -0
  33. data/lib/iota/crypto/kerl.rb +82 -0
  34. data/lib/iota/crypto/pow_provider.rb +27 -0
  35. data/lib/iota/crypto/private_key.rb +80 -0
  36. data/lib/iota/crypto/sha3_ruby.rb +122 -0
  37. data/lib/iota/crypto/signing.rb +97 -0
  38. data/lib/iota/models/account.rb +489 -0
  39. data/lib/iota/models/base.rb +13 -0
  40. data/lib/iota/models/bundle.rb +87 -0
  41. data/lib/iota/models/input.rb +38 -0
  42. data/lib/iota/models/seed.rb +33 -0
  43. data/lib/iota/models/transaction.rb +52 -0
  44. data/lib/iota/models/transfer.rb +44 -0
  45. data/lib/iota/multisig/address.rb +41 -0
  46. data/lib/iota/multisig/multisig.rb +244 -0
  47. data/lib/iota/utils/ascii.rb +50 -0
  48. data/lib/iota/utils/broker.rb +124 -0
  49. data/lib/iota/utils/input_validator.rb +149 -0
  50. data/lib/iota/utils/object_validator.rb +34 -0
  51. data/lib/iota/utils/utils.rb +324 -0
  52. data/lib/iota/version.rb +3 -0
  53. data/lib/jcurl.jar +0 -0
  54. data/lib/patch.rb +17 -0
  55. data/test/ascii_test.rb +114 -0
  56. data/test/curl_c_test.rb +31 -0
  57. data/test/curl_java_test.rb +31 -0
  58. data/test/curl_ruby_test.rb +27 -0
  59. data/test/kerl_test.rb +52 -0
  60. data/test/pow_provider_test.rb +36 -0
  61. data/test/sha3_test.rb +71 -0
  62. data/test/test_helper.rb +4 -0
  63. data/test/utils_test.rb +179 -0
  64. metadata +183 -0
@@ -0,0 +1,489 @@
1
+ module IOTA
2
+ module Models
3
+ class Account < Base
4
+ attr_reader :client, :seed, :latestAddress, :addresses, :transfers, :inputs, :balance
5
+
6
+ def initialize(client, seed, api = nil, validator = nil, utils = nil)
7
+ @api = client ? client.api : api
8
+ @validator = client ? client.validator : validator
9
+ @utils = client ? client.utils : utils
10
+ @seed = seed.class == String ? Seed.new(seed) : seed
11
+
12
+ reset
13
+ end
14
+
15
+ def reset
16
+ @latestAddress = nil
17
+ @addresses = []
18
+ @transfers = []
19
+ @inputs = []
20
+ @balance = 0.0
21
+ end
22
+
23
+ def getAccountDetails(options = {}, fetchInputs = true, fetchTransfers = true)
24
+ reset
25
+ options = symbolize_keys(options)
26
+ startIndex = options[:start] || 0
27
+ endIndex = options[:end] || nil
28
+ security = options[:security] || 2
29
+
30
+ # If start value bigger than end, return error or if difference between end and start is bigger than 1000 keys
31
+ if endIndex && (startIndex > endIndex || endIndex > (startIndex + 1000))
32
+ raise StandardError, "Invalid inputs provided"
33
+ end
34
+
35
+ addressOptions = {
36
+ index: startIndex,
37
+ total: endIndex ? endIndex - startIndex : nil,
38
+ returnAll: true,
39
+ security: security,
40
+ checksum: true
41
+ }
42
+
43
+ # Get a list of all addresses associated with the users seed
44
+ addresses = getNewAddress(addressOptions)
45
+
46
+ # assign the last address as the latest address
47
+ # since it has no transactions associated with it
48
+ @latestAddress = addresses.last
49
+
50
+ # Add all returned addresses to the lsit of addresses
51
+ # remove the last element as that is the most recent address
52
+ @addresses = addresses.slice(0, addresses.length)
53
+
54
+ if fetchInputs
55
+ # Get the correct balance count of all addresses
56
+ @api.getBalances(addresses, 100) do |status2, balancesData|
57
+ if !status2
58
+ raise StandardError, balancesData
59
+ end
60
+
61
+ balancesData.each_with_index do |balance, index|
62
+ balance = balance.to_i
63
+ @balance += balance
64
+
65
+ if balance > 0
66
+ @inputs << IOTA::Models::Input.new(
67
+ address: @addresses[index],
68
+ keyIndex: index,
69
+ security: security,
70
+ balance: balance
71
+ )
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ if fetchTransfers
78
+ # get all bundles from a list of addresses
79
+ @api.bundlesFromAddresses(addresses, true) do |status1, bundlesData|
80
+ if !status1
81
+ raise StandardError, bundlesData
82
+ end
83
+
84
+ # add all transfers
85
+ @transfers = bundlesData
86
+ end
87
+ end
88
+
89
+ self
90
+ end
91
+
92
+ def getInputs(options = {})
93
+ self.getAccountDetails(options, true, false)
94
+ @inputs
95
+ end
96
+
97
+ def getTransfers(options = {})
98
+ self.getAccountDetails(options, false, true)
99
+ @transfers
100
+ end
101
+
102
+ def prepareTransfers(transfers, options = {})
103
+ options = symbolize_keys(options)
104
+ hmacKey = options[:hmacKey] || nil
105
+ if !hmacKey.nil?
106
+ raise StandardError, "Invalid trytes provided: #{hmacKey}" if !@validator.isTrytes(hmacKey)
107
+ end
108
+
109
+ # If message or tag is not supplied, provide it
110
+ # Also remove the checksum of the address if it's there after validating it
111
+ (0...transfers.length).step(1) do |index|
112
+ if transfers[index].class != IOTA::Models::Transfer
113
+ transfers[index] = IOTA::Models::Transfer.new(transfers[index].merge({ hmacKey: hmacKey }))
114
+ end
115
+ end
116
+
117
+ # Input validation of transfers object
118
+ raise StandardError, "Invalid transfers provided" if !@validator.isTransfersArray(transfers)
119
+
120
+ # If inputs provided, validate the format
121
+ chosenInputs = options[:inputs] || nil
122
+ raise StandardError, "Invalid inputs provided" if chosenInputs && !@validator.isInputs(chosenInputs)
123
+
124
+ remainderAddress = options[:address] || nil
125
+ chosenInputs = chosenInputs || []
126
+ security = options[:security] || 2
127
+
128
+ remainderAddress = @utils.noChecksum(remainderAddress) if remainderAddress && remainderAddress.length == 90
129
+
130
+ # Create a new bundle
131
+ bundle = IOTA::Crypto::Bundle.new
132
+
133
+ totalValue = 0
134
+ signatureFragments = []
135
+ tag = nil
136
+
137
+ # Iterate over all transfers, get totalValue and prepare the signatureFragments, message and tag
138
+ (0...transfers.length).step(1) do |i|
139
+ signatureMessageLength = 1
140
+
141
+ # If message longer than 2187 trytes, increase signatureMessageLength (add 2nd transaction)
142
+ if transfers[i].message.length > 2187
143
+ # Get total length, message / maxLength (2187 trytes)
144
+ signatureMessageLength += (transfers[i].message.length / 2187).floor
145
+ msgCopy = transfers[i].message
146
+
147
+ # While there is still a message, copy it
148
+ while msgCopy
149
+ fragment = msgCopy[0...2187]
150
+ msgCopy = msgCopy[2187...msgCopy.length]
151
+
152
+ # Pad remainder of fragment
153
+ while fragment.length < 2187
154
+ fragment += '9'
155
+ end
156
+
157
+ signatureFragments << fragment
158
+ end
159
+ else
160
+ # Else, get single fragment with 2187 of 9's trytes
161
+ fragment = ''
162
+
163
+ fragment = transfers[i].message[0...2187] if transfers[i].message
164
+
165
+ while fragment.length < 2187
166
+ fragment += '9'
167
+ end
168
+
169
+ signatureFragments << fragment
170
+ end
171
+
172
+ # get current timestamp in seconds
173
+ timestamp = Time.now.utc.to_i
174
+
175
+ # If no tag defined, get 27 tryte tag.
176
+ tag = transfers[i].obsoleteTag || ''
177
+
178
+ # Pad for required 27 tryte length
179
+ while tag.length < 27
180
+ tag += '9'
181
+ end
182
+
183
+ # Add first entries to the bundle
184
+ # Slice the address in case the user provided a checksummed one
185
+ bundle.addEntry(signatureMessageLength, transfers[i].address, transfers[i].value, tag, timestamp)
186
+ # Sum up total value
187
+ totalValue += transfers[i].value.to_i
188
+ end
189
+
190
+ # Get inputs if we are sending tokens
191
+ if totalValue > 0
192
+ # Case 1: user provided inputs
193
+ #
194
+ # Validate the inputs by calling getBalances
195
+ if chosenInputs && chosenInputs.length > 0
196
+ # Get list if addresses of the provided inputs
197
+ inputsAddresses = [];
198
+ (0...chosenInputs.length).step(1) do |i|
199
+ if chosenInputs[i].class != IOTA::Models::Input
200
+ chosenInputs[i] = IOTA::Models::Input.new(chosenInputs[i])
201
+ end
202
+ inputsAddresses << chosenInputs[i].address
203
+ end
204
+
205
+ @api.getBalances(inputsAddresses, 100) do |status, balances|
206
+ raise StandardError, balances if !status
207
+
208
+ confirmedInputs = []
209
+ totalBalance = 0
210
+
211
+ balances.each_with_index do |balance, i|
212
+ # If input has balance, add it to confirmedInputs
213
+ balance = balance.to_i
214
+
215
+ if balance > 0
216
+ totalBalance += balance
217
+
218
+ input = chosenInputs[i]
219
+ input.balance = balance
220
+
221
+ confirmedInputs << input
222
+
223
+ # if we've already reached the intended input value, break out of loop
224
+ if totalBalance >= totalValue
225
+ break
226
+ end
227
+ end
228
+ end
229
+
230
+ # Return not enough balance error
231
+ raise StandardError, "Not enough balance" if totalValue > totalBalance
232
+
233
+ return addRemainder(confirmedInputs, totalValue, bundle, tag, security, signatureFragments, remainderAddress, hmacKey)
234
+ end
235
+ else
236
+ # Case 2: Get inputs deterministically
237
+ # If no inputs provided, derive the addresses from the seed and
238
+ # confirm that the inputs exceed the threshold
239
+ self.getInputs(security: security)
240
+
241
+ raise StandardError, "Not enough balance" if totalValue > @balance
242
+
243
+ return addRemainder(@inputs, totalValue, bundle, tag, security, signatureFragments, remainderAddress, hmacKey)
244
+ end
245
+ else
246
+ # If no input required, don't sign and simply finalize the bundle
247
+ bundle.finalize()
248
+ bundle.addTrytes(signatureFragments)
249
+
250
+ bundleTrytes = []
251
+ bundle.bundle.each do |bndl|
252
+ bundleTrytes << @utils.transactionTrytes(bndl)
253
+ end
254
+
255
+ return bundleTrytes.reverse
256
+ end
257
+ end
258
+
259
+ def sendTransfer(depth, minWeightMagnitude, transfers, options = {})
260
+ # Check if correct depth and minWeightMagnitude
261
+ if !@validator.isValue(depth) || !@validator.isValue(minWeightMagnitude)
262
+ raise StandardError, "Invalid inputs provided"
263
+ end
264
+
265
+ trytes = prepareTransfers(transfers, options)
266
+
267
+ @api.sendTrytes(trytes, depth, minWeightMagnitude, options) do |status, data|
268
+ if !status
269
+ raise StandardError, data
270
+ else
271
+ return data
272
+ end
273
+ end
274
+ end
275
+
276
+ def getNewAddress(options = {})
277
+ # default index value
278
+ options = symbolize_keys(options)
279
+ index = options[:index] || 0
280
+
281
+ # validate the index option
282
+ if !@validator.isValue(index) || index < 0
283
+ raise StandardError, "Invalid index provided: #{index}"
284
+ end
285
+
286
+ checksum = options[:checksum] || false
287
+ total = options[:total] || nil
288
+ return_all = options[:returnAll] || false
289
+
290
+ # If no user defined security, use the standard value of 2
291
+ security = options[:security] || 2
292
+
293
+ # validate the security option
294
+ if !@validator.isValue(security) || security < 1 || security > 3
295
+ raise StandardError, "Invalid security provided: #{index}"
296
+ end
297
+
298
+ allAddresses = []
299
+
300
+ # Case 1: total
301
+ #
302
+ # If total number of addresses to generate is supplied, simply generate
303
+ # and return the list of all addresses
304
+ if total
305
+ # Increase index with each iteration
306
+ (0...total).step(1) do |i|
307
+ address = @seed.getAddress(index, security, checksum)
308
+ allAddresses << address
309
+ index += 1
310
+ end
311
+
312
+ return allAddresses
313
+ else
314
+ # Case 2: no total provided
315
+ #
316
+ # Continue calling findTransactions to see if address was already created
317
+ # if null, return list of addresses
318
+ loop do
319
+ newAddress = @seed.getAddress(index, security, checksum)
320
+
321
+ @api.wereAddressesSpentFrom([newAddress]) do |status, spent_states|
322
+ if !status
323
+ raise StandardError, spent_states
324
+ end
325
+
326
+ spent = spent_states[0]
327
+
328
+ # Check transactions if not spent
329
+ if !spent
330
+ @api.findTransactions(addresses: [newAddress]) do |status, trx_hashes|
331
+ if !status
332
+ raise StandardError, trx_hashes
333
+ end
334
+
335
+ spent = trx_hashes.length > 0
336
+ end
337
+ end
338
+
339
+ if return_all
340
+ allAddresses << newAddress
341
+ end
342
+
343
+ index += 1
344
+ return (return_all ? allAddresses : newAddress) if !spent
345
+ end
346
+ end
347
+ end
348
+ end
349
+
350
+ private
351
+ def addRemainder(inputs, totalValue, bundle, tag, security, signatureFragments, remainderAddress, hmacKey)
352
+ totalTransferValue = totalValue
353
+ inputs.each do |input|
354
+ balance = input.balance
355
+ timestamp = Time.now.utc.to_i
356
+
357
+ # Add input as bundle entry
358
+ bundle.addEntry(input.security, input.address, -balance, tag, timestamp)
359
+
360
+ # If there is a remainder value
361
+ # Add extra output to send remaining funds to
362
+ if balance >= totalTransferValue
363
+ remainder = balance - totalTransferValue
364
+
365
+ # If user has provided remainder address
366
+ # Use it to send remaining funds to
367
+ if remainder > 0 && remainderAddress
368
+ # Remainder bundle entry
369
+ bundle.addEntry(1, remainderAddress, remainder, tag, timestamp)
370
+
371
+ # Final function for signing inputs
372
+ return signInputs(inputs, bundle, signatureFragments, hmacKey)
373
+ elsif remainder > 0
374
+ index = 0
375
+ (0...inputs.length).step(1) do |k|
376
+ index = [inputs[k].keyIndex, index].max
377
+ end
378
+ index += 1
379
+
380
+ # Generate a new Address by calling getNewAddress
381
+ address = getNewAddress({index: index, security: security})
382
+ timestamp = Time.now.utc.to_i
383
+
384
+ # Remainder bundle entry
385
+ bundle.addEntry(1, address, remainder, tag, timestamp)
386
+
387
+ # Final function for signing inputs
388
+ return signInputs(inputs, bundle, signatureFragments, hmacKey)
389
+ else
390
+ # If there is no remainder, do not add transaction to bundle simply sign and return
391
+ return signInputs(inputs, bundle, signatureFragments, hmacKey)
392
+ end
393
+ else
394
+ # If multiple inputs provided, subtract the totalTransferValue by the inputs balance
395
+ totalTransferValue -= balance
396
+ end
397
+ end
398
+ end
399
+
400
+ def signInputs(inputs, bundle, signatureFragments, hmacKey)
401
+ bundle.finalize()
402
+ bundle.addTrytes(signatureFragments)
403
+
404
+ # SIGNING OF INPUTS
405
+ # Here we do the actual signing of the inputs
406
+ # Iterate over all bundle transactions, find the inputs
407
+ # Get the corresponding private key and calculate the signatureFragment
408
+
409
+ (0...bundle.bundle.length).step(1) do |i|
410
+ trx = bundle.bundle[i]
411
+
412
+ if trx.value < 0
413
+ address = trx.address
414
+
415
+ # Get the corresponding keyIndex and security of the address
416
+ keyIndex = nil
417
+ keySecurity = nil
418
+ inputs.each do |input|
419
+ if input.address == address
420
+ keyIndex = input.keyIndex
421
+ keySecurity = input.security ? input.security : security
422
+ break
423
+ end
424
+ end
425
+
426
+ # Get corresponding private key of address
427
+ privateKey = IOTA::Crypto::PrivateKey.new(@seed.as_trits, keyIndex, keySecurity)
428
+ key = privateKey.key
429
+
430
+ # Get the normalized bundle hash
431
+ normalizedBundleHash = bundle.normalizedBundle(trx.bundle)
432
+ normalizedBundleFragments = []
433
+
434
+ # Split hash into 3 fragments
435
+ (0...3).step(1) do |l|
436
+ normalizedBundleFragments[l] = normalizedBundleHash.slice(l * 27, 27)
437
+ end
438
+
439
+ # First 6561 trits for the firstFragment
440
+ firstFragment = key[0...6561]
441
+
442
+ # First bundle fragment uses the first 27 trytes
443
+ firstBundleFragment = normalizedBundleFragments[0]
444
+
445
+ # Calculate the new signatureFragment with the first bundle fragment
446
+ firstSignedFragment = IOTA::Crypto::Signing.signatureFragment(firstBundleFragment, firstFragment)
447
+
448
+ # Convert signature to trytes and assign the new signatureFragment
449
+ trx.signatureMessageFragment = IOTA::Crypto::Converter.trytes(firstSignedFragment)
450
+
451
+ # if user chooses higher than 27-tryte security
452
+ # for each security level, add an additional signature
453
+ (1...keySecurity).step(1) do |j|
454
+ # Because the signature is > 2187 trytes, we need to
455
+ # find the subsequent transaction to add the remainder of the signature
456
+ # Same address as well as value = 0 (as we already spent the input)
457
+ if bundle.bundle[i + j].address == address && bundle.bundle[i + j].value == 0
458
+ # Use the next 6561 trits
459
+ nextFragment = key.slice(6561 * j, 6561)
460
+
461
+ nextBundleFragment = normalizedBundleFragments[j]
462
+
463
+ # Calculate the new signature
464
+ nextSignedFragment = IOTA::Crypto::Signing.signatureFragment(nextBundleFragment, nextFragment)
465
+
466
+ # Convert signature to trytes and assign it again to this bundle entry
467
+ bundle.bundle[i + j].signatureMessageFragment = IOTA::Crypto::Converter.trytes(nextSignedFragment)
468
+ end
469
+ end
470
+ end
471
+ end
472
+
473
+ if hmacKey
474
+ hmac = IOTA::Crypto::Hmac(hmacKey)
475
+ hmac.addHMAC(bundle)
476
+ end
477
+
478
+ bundleTrytes = []
479
+
480
+ # Convert all bundle entries into trytes
481
+ bundle.bundle.each do |bndl|
482
+ bundleTrytes << @utils.transactionTrytes(bndl)
483
+ end
484
+
485
+ bundleTrytes.reverse
486
+ end
487
+ end
488
+ end
489
+ end