iota-ruby 1.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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