iota-ruby 1.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,402 @@
1
+ module IOTA
2
+ module API
3
+ module Wrappers
4
+ def getTransactionsObjects(hashes, &callback)
5
+ # If not array of hashes, return error
6
+ if !@validator.isArrayOfHashes(hashes)
7
+ return sendData(false, "Invalid inputs provided", &callback)
8
+ end
9
+
10
+ ret_status = false
11
+ ret_data = nil
12
+ # get the trytes of the transaction hashes
13
+ getTrytes(hashes) do |status, trytes|
14
+ ret_status = status
15
+ if status
16
+ transactionObjects = []
17
+ trytes.each do |tryte|
18
+ if !tryte
19
+ transactionObjects << nil
20
+ else
21
+ transactionObjects << @utils.transactionObject(tryte)
22
+ end
23
+ end
24
+ ret_data = transactionObjects
25
+ else
26
+ ret_data = trytes
27
+ end
28
+ end
29
+
30
+ sendData(ret_status, ret_data, &callback)
31
+ end
32
+
33
+ def findTransactionObjects(input, &callback)
34
+ findTransactions(input) do |status, transactions|
35
+ if !status
36
+ return sendData(status, transactions, &callback)
37
+ else
38
+ return getTransactionsObjects(transactions, &callback)
39
+ end
40
+ end
41
+ end
42
+
43
+ def getLatestInclusion(hashes, &callback)
44
+ ret_status = false
45
+ ret_data = nil
46
+ getNodeInfo do |status, data|
47
+ if status
48
+ ret_status = true
49
+ ret_data = data['latestSolidSubtangleMilestone']
50
+ end
51
+ end
52
+
53
+ if ret_status
54
+ return getInclusionStates(hashes, [ret_data], &callback)
55
+ else
56
+ return sendData(ret_status, ret_data, &callback)
57
+ end
58
+ end
59
+
60
+ def storeAndBroadcast(trytes, &callback)
61
+ storeTransactions(trytes) do |status, data|
62
+ if !status
63
+ return sendData(status, data, &callback)
64
+ else
65
+ return broadcastTransactions(trytes, &callback)
66
+ end
67
+ end
68
+ end
69
+
70
+ def sendTrytes(trytes, depth, minWeightMagnitude, &callback)
71
+ # Check if correct depth and minWeightMagnitude
72
+ if !@validator.isValue(depth) || !@validator.isValue(minWeightMagnitude)
73
+ return sendData(false, "Invalid inputs provided", &callback)
74
+ end
75
+
76
+ getTransactionsToApprove(depth) do |status, approval_data|
77
+ if !status
78
+ return sendData(false, approval_data, &callback)
79
+ end
80
+
81
+ attachToTangle(approval_data['trunkTransaction'], approval_data['branchTransaction'], minWeightMagnitude, trytes) do |status1, attached_data|
82
+ if !status1
83
+ return sendData(false, attached_data, &callback)
84
+ end
85
+
86
+ # If the user is connected to the sandbox, we have to monitor the POW queue
87
+ # to check if the POW job was completed
88
+ if @sandbox
89
+ # Implement sandbox processing
90
+ jobUri = @sandbox + '/jobs/' + attached_data['id']
91
+
92
+ # Do the Sandbox send function
93
+ @broker.sandboxSend(jobUri) do |status2, sandbox_data|
94
+ if !status2
95
+ return sendData(false, sandbox_data, &callback)
96
+ end
97
+
98
+ storeAndBroadcast(sandbox_data) do |status3, data|
99
+ if status3
100
+ return sendData(false, data, &callback)
101
+ end
102
+
103
+ finalTxs = []
104
+
105
+ attachedTrytes.each do |trytes1|
106
+ finalTxs << @utils.transactionObject(trytes1)
107
+ end
108
+
109
+ return sendData(true, finalTxs, &callback)
110
+ end
111
+ end
112
+ else
113
+ # Broadcast and store tx
114
+ storeAndBroadcast(attached_data) do |status2, data|
115
+ if !status2
116
+ return sendData(false, data, &callback)
117
+ end
118
+
119
+ transactions = attached_data.map { |tryte| @utils.transactionObject(tryte) }
120
+
121
+ sendData(true, transactions, &callback)
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
127
+
128
+ def bundlesFromAddresses(addresses, inclusionStates, &callback)
129
+ # call wrapper function to get txs associated with addresses
130
+ findTransactionObjects(addresses: addresses) do |status, transactionObjects|
131
+ if !status
132
+ return sendData(false, transactionObjects, &callback)
133
+ end
134
+
135
+ # set of tail transactions
136
+ tailTransactions = []
137
+ nonTailBundleHashes = []
138
+
139
+ transactionObjects.each do |trx|
140
+ # Sort tail and nonTails
141
+ if trx.currentIndex == 0
142
+ tailTransactions << trx.hash
143
+ else
144
+ nonTailBundleHashes << trx.bundle
145
+ end
146
+ end
147
+
148
+ # Get tail transactions for each nonTail via the bundle hash
149
+ findTransactionObjects(bundles: nonTailBundleHashes.uniq) do |st1, trxObjects|
150
+ if !st1
151
+ return sendData(false, trxObjects, &callback)
152
+ end
153
+
154
+ trxObjects.each do |trx|
155
+ tailTransactions << trx.hash if trx.currentIndex == 0
156
+ end
157
+
158
+ finalBundles = []
159
+ tailTransactions = tailTransactions.uniq
160
+ tailTxStates = []
161
+
162
+ # If inclusionStates, get the confirmation status of the tail transactions, and thus the bundles
163
+ if inclusionStates && tailTransactions.length > 0
164
+ getLatestInclusion(tailTransactions) do |st2, states|
165
+ # If error, return it to original caller
166
+ if !status
167
+ return sendData(false, states, &callback)
168
+ end
169
+
170
+ tailTxStates = states
171
+ end
172
+ end
173
+
174
+ tailTransactions.each do |tailTx|
175
+ getBundle(tailTx) do |st2, bundleTransactions|
176
+ if st2
177
+ if inclusionStates
178
+ thisInclusion = tailTxStates[tailTransactions.index(tailTx)]
179
+
180
+ bundleTransactions.each do |bundleTx|
181
+ bundleTx.persistence = thisInclusion
182
+ end
183
+ end
184
+
185
+ finalBundles << IOTA::Models::Bundle.new(bundleTransactions)
186
+ end
187
+ end
188
+ end
189
+ # Sort bundles by attachmentTimestamp
190
+ finalBundles = finalBundles.sort{|a, b| a.attachmentTimestamp <=> b.attachmentTimestamp}
191
+ return sendData(true, finalBundles, &callback)
192
+ end
193
+ end
194
+ end
195
+
196
+ def getBundle(transaction, &callback)
197
+ # Check if correct hash
198
+ if !@validator.isHash(transaction)
199
+ return sendData(false, "Invalid transaction input provided", &callback)
200
+ end
201
+
202
+ # Initiate traverseBundle
203
+ traverseBundle(transaction, nil, []) do |status, bundle|
204
+ if !status
205
+ return sendData(false, bundle, &callback)
206
+ end
207
+
208
+ if !@utils.isBundle(bundle)
209
+ return sendData(false, "Invalid Bundle provided", &callback)
210
+ end
211
+
212
+ return sendData(true, bundle, &callback)
213
+ end
214
+ end
215
+
216
+ def replayBundle(tail, depth, minWeightMagnitude, &callback)
217
+ # Check if correct tail hash
218
+ if !@validator.isHash(tail)
219
+ return sendData(false, "Invalid trytes provided", &callback)
220
+ end
221
+
222
+ # Check if correct depth and minWeightMagnitude
223
+ if !@validator.isValue(depth) || !@validator.isValue(minWeightMagnitude)
224
+ return sendData(false, "Invalid inputs provided", &callback)
225
+ end
226
+
227
+ getBundle(tail) do |status, transactions|
228
+ if !status
229
+ return sendData(false, transactions, &callback)
230
+ end
231
+
232
+ bundleTrytes = []
233
+ transactions.each do |trx|
234
+ bundleTrytes << @utils.transactionTrytes(trx);
235
+ end
236
+
237
+ return sendTrytes(bundleTrytes.reverse, depth, minWeightMagnitude, &callback)
238
+ end
239
+ end
240
+
241
+ def broadcastBundle(tail, &callback)
242
+ # Check if correct tail hash
243
+ if !@validator.isHash(tail)
244
+ return sendData(false, "Invalid trytes provided", &callback)
245
+ end
246
+
247
+ getBundle(tail) do |status, transactions|
248
+ if !status
249
+ return sendData(false, transactions, &callback)
250
+ end
251
+
252
+ bundleTrytes = []
253
+ transactions.each do |trx|
254
+ bundleTrytes << @utils.transactionTrytes(trx);
255
+ end
256
+
257
+ return broadcastTransactions(bundleTrytes.reverse, &callback)
258
+ end
259
+ end
260
+
261
+ def traverseBundle(trunkTx, bundleHash, bundle, &callback)
262
+ # Get trytes of transaction hash
263
+ getTrytes([trunkTx]) do |status, trytesList|
264
+ if !status
265
+ return sendData(false, trytesList, &callback)
266
+ end
267
+
268
+ trytes = trytesList[0]
269
+
270
+ if !trytes
271
+ return sendData(false, "Bundle transactions not visible", &callback)
272
+ end
273
+
274
+ # get the transaction object
275
+ txObject = @utils.transactionObject(trytes)
276
+
277
+ if !trytes
278
+ return sendData(false, "Invalid trytes, could not create object", &callback)
279
+ end
280
+
281
+ # If first transaction to search is not a tail, return error
282
+ if !bundleHash && txObject.currentIndex != 0
283
+ return sendData(false, "Invalid tail transaction supplied", &callback)
284
+ end
285
+
286
+ # If no bundle hash, define it
287
+ if !bundleHash
288
+ bundleHash = txObject.bundle
289
+ end
290
+
291
+ # If different bundle hash, return with bundle
292
+ if bundleHash != txObject.bundle
293
+ return sendData(true, bundle, &callback)
294
+ end
295
+
296
+ # If only one bundle element, return
297
+ if txObject.lastIndex == 0 && txObject.currentIndex == 0
298
+ return sendData(true, [txObject], &callback)
299
+ end
300
+
301
+ # Define new trunkTransaction for search
302
+ trunkTx = txObject.trunkTransaction
303
+
304
+ # Add transaction object to bundle
305
+ bundle << txObject
306
+
307
+ # Continue traversing with new trunkTx
308
+ return traverseBundle(trunkTx, bundleHash, bundle, &callback)
309
+ end
310
+ end
311
+
312
+ def isReattachable(inputAddresses, &callback)
313
+ # if string provided, make array
314
+ inputAddresses = [inputAddresses] if @validator.isString(inputAddresses)
315
+
316
+ # Categorized value transactions
317
+ # hash -> txarray map
318
+ addressTxsMap = {}
319
+ addresses = []
320
+
321
+ inputAddresses.each do |address|
322
+ if !@validator.isAddress(address)
323
+ return sendData(false, "Invalid inputs provided", &callback)
324
+ end
325
+
326
+ address = @utils.noChecksum(address)
327
+
328
+ addressTxsMap[address] = []
329
+ addresses << address
330
+ end
331
+
332
+ findTransactionObjects(addresses: addresses) do |status, transactions|
333
+ if !status
334
+ return sendData(false, transactions, &callback)
335
+ end
336
+
337
+ valueTransactions = []
338
+
339
+ transactions.each do |trx|
340
+ if trx.value < 0
341
+ txAddress = trx.address
342
+ txHash = trx.hash
343
+
344
+ addressTxsMap[txAddress] << txHash
345
+ valueTransactions << txHash
346
+ end
347
+ end
348
+
349
+ if valueTransactions.length > 0
350
+ # get the includion states of all the transactions
351
+ getLatestInclusion(valueTransactions) do |st1, inclusionStates|
352
+ if !st1
353
+ return sendData(false, inclusionStates, &callback)
354
+ end
355
+
356
+ # bool array
357
+ results = addresses.map do |address|
358
+ txs = addressTxsMap[address]
359
+ numTxs = txs.length
360
+
361
+ if numTxs == 0
362
+ true
363
+ else
364
+ shouldReattach = true
365
+
366
+ (0...numTxs).step(1) do |i|
367
+ tx = txs[i]
368
+ txIndex = valueTransactions.index(tx)
369
+ isConfirmed = inclusionStates[txIndex]
370
+ shouldReattach = isConfirmed ? false : true
371
+ break if isConfirmed
372
+ end
373
+
374
+ shouldReattach
375
+ end
376
+ end
377
+
378
+ # If only one entry, return first
379
+ results = results.first if results.length == 1
380
+
381
+ return sendData(true, results, &callback)
382
+ end
383
+ else
384
+ results = [];
385
+ numAddresses = addresses.length;
386
+
387
+ # prepare results array if multiple addresses
388
+ if numAddresses > 1
389
+ numAddresses.each do |i|
390
+ results << true
391
+ end
392
+ else
393
+ results = true
394
+ end
395
+
396
+ return sendData(true, results, &callback)
397
+ end
398
+ end
399
+ end
400
+ end
401
+ end
402
+ end
@@ -0,0 +1,163 @@
1
+ module IOTA
2
+ module Crypto
3
+ class Bundle
4
+ attr_reader :bundle
5
+
6
+ def initialize(bundle = [])
7
+ bundle = bundle.map do |b|
8
+ if b.class != IOTA::Models::Transaction
9
+ IOTA::Models::Transaction.new(b)
10
+ else
11
+ b
12
+ end
13
+ end
14
+
15
+ @bundle = bundle
16
+ end
17
+
18
+ def addEntry(signatureMessageLength, address, value, tag, timestamp)
19
+ (0...signatureMessageLength).step(1) do |i|
20
+ transactionObject = IOTA::Models::Transaction.new(
21
+ address: address,
22
+ value: i==0 ? value : 0,
23
+ obsoleteTag: tag,
24
+ tag: tag,
25
+ timestamp: timestamp
26
+ )
27
+
28
+ @bundle << transactionObject
29
+ end
30
+ end
31
+
32
+ def addTrytes(signatureFragments)
33
+ emptySignatureFragment = ''
34
+ emptyHash = '9' * 81
35
+ emptyTag = '9' * 27
36
+ emptyTimestamp = '9' * 9
37
+
38
+ while emptySignatureFragment.length < 2187
39
+ emptySignatureFragment += '9'
40
+ end
41
+
42
+ (0...@bundle.length).step(1) do |i|
43
+ # Fill empty signatureMessageFragment
44
+ @bundle[i].signatureMessageFragment = signatureFragments[i] ? signatureFragments[i] : emptySignatureFragment
45
+
46
+ # Fill empty trunkTransaction
47
+ @bundle[i].trunkTransaction = emptyHash
48
+
49
+ # Fill empty branchTransaction
50
+ @bundle[i].branchTransaction = emptyHash
51
+
52
+ @bundle[i].attachmentTimestamp = emptyTimestamp
53
+ @bundle[i].attachmentTimestampLowerBound = emptyTimestamp
54
+ @bundle[i].attachmentTimestampUpperBound = emptyTimestamp
55
+
56
+ # Fill empty nonce
57
+ @bundle[i].nonce = emptyTag
58
+ end
59
+ end
60
+
61
+ def finalize
62
+ validBundle = false
63
+
64
+ while !validBundle
65
+
66
+ kerl = Kerl.new
67
+
68
+ (0...@bundle.length).step(1) do |i|
69
+ valueTrits = Converter.trits(@bundle[i].value)
70
+ while valueTrits.length < 81
71
+ valueTrits << 0
72
+ end
73
+
74
+ timestampTrits = Converter.trits(@bundle[i].timestamp)
75
+ while timestampTrits.length < 27
76
+ timestampTrits << 0
77
+ end
78
+
79
+ @bundle[i].currentIndex = i
80
+ currentIndexTrits = Converter.trits(@bundle[i].currentIndex)
81
+ while currentIndexTrits.length < 27
82
+ currentIndexTrits << 0
83
+ end
84
+
85
+ @bundle[i].lastIndex = @bundle.length - 1
86
+ lastIndexTrits = Converter.trits(@bundle[i].lastIndex)
87
+ while lastIndexTrits.length < 27
88
+ lastIndexTrits << 0
89
+ end
90
+
91
+ bundleEssence = Converter.trits(@bundle[i].address + Converter.trytes(valueTrits) + @bundle[i].obsoleteTag + Converter.trytes(timestampTrits) + Converter.trytes(currentIndexTrits) + Converter.trytes(lastIndexTrits))
92
+ kerl.absorb(bundleEssence, 0, bundleEssence.length)
93
+ end
94
+
95
+ hash = []
96
+ kerl.squeeze(hash, 0, Kerl::HASH_LENGTH)
97
+ hash = Converter.trytes(hash)
98
+
99
+ (0...@bundle.length).step(1) do |i|
100
+ @bundle[i].bundle = hash
101
+ end
102
+
103
+ normalizedHash = normalizedBundle(hash)
104
+ if !normalizedHash.index(13).nil?
105
+ # Insecure bundle. Increment Tag and recompute bundle hash.
106
+ increasedTagTrits = Converter.trits(@bundle[0].obsoleteTag)
107
+
108
+ # Adder implementation with 1 round
109
+ (0...increasedTagTrits.length).step(1) do |j|
110
+ increasedTagTrits[j] += 1
111
+
112
+ if increasedTagTrits[j] > 1
113
+ increasedTagTrits[j] = -1
114
+ else
115
+ break
116
+ end
117
+ end
118
+
119
+ @bundle[0].obsoleteTag = Converter.trytes(increasedTagTrits)
120
+ else
121
+ validBundle = true
122
+ end
123
+ end
124
+ end
125
+
126
+ def normalizedBundle(bundleHash)
127
+ normalizedBundleArr = []
128
+
129
+ (0...3).step(1) do |i|
130
+ sum = 0
131
+ (0...27).step(1) do |j|
132
+ normalizedBundleArr[i * 27 + j] = Converter.value(Converter.trits(bundleHash[i * 27 + j]))
133
+ sum += normalizedBundleArr[i * 27 + j]
134
+ end
135
+
136
+ if sum >= 0
137
+ while sum > 0
138
+ sum -= 1
139
+ (0...27).step(1) do |j|
140
+ if normalizedBundleArr[i * 27 + j] > -13
141
+ normalizedBundleArr[i * 27 + j] -= 1
142
+ break
143
+ end
144
+ end
145
+ end
146
+ else
147
+ while sum < 0
148
+ sum += 1
149
+ (0...27).step(1) do |j|
150
+ if normalizedBundleArr[i * 27 + j] < 13
151
+ normalizedBundleArr[i * 27 + j] += 1
152
+ break
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
158
+
159
+ normalizedBundleArr
160
+ end
161
+ end
162
+ end
163
+ end