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.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +15 -0
- data/.travis.yml +24 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/Rakefile +36 -0
- data/bin/iota-console +15 -0
- data/examples/multisig.rb +69 -0
- data/ext/ccurl/ccurl.c +134 -0
- data/ext/ccurl/extconf.rb +22 -0
- data/ext/jcurl/JCurl.java +126 -0
- data/ext/jcurl/JCurlService.java +36 -0
- data/ext/pow/ccurl-0.3.0.dll +0 -0
- data/ext/pow/libccurl-0.3.0.dylib +0 -0
- data/ext/pow/libccurl-0.3.0.so +0 -0
- data/iota-ruby.gemspec +37 -0
- data/lib/iota.rb +76 -0
- data/lib/iota/api/api.rb +251 -0
- data/lib/iota/api/commands.rb +113 -0
- data/lib/iota/api/transport.rb +43 -0
- data/lib/iota/api/wrappers.rb +429 -0
- data/lib/iota/crypto/bundle.rb +163 -0
- data/lib/iota/crypto/converter.rb +244 -0
- data/lib/iota/crypto/curl.rb +18 -0
- data/lib/iota/crypto/curl_c.rb +17 -0
- data/lib/iota/crypto/curl_java.rb +18 -0
- data/lib/iota/crypto/curl_ruby.rb +70 -0
- data/lib/iota/crypto/hmac.rb +27 -0
- data/lib/iota/crypto/kerl.rb +82 -0
- data/lib/iota/crypto/pow_provider.rb +27 -0
- data/lib/iota/crypto/private_key.rb +80 -0
- data/lib/iota/crypto/sha3_ruby.rb +122 -0
- data/lib/iota/crypto/signing.rb +97 -0
- data/lib/iota/models/account.rb +489 -0
- data/lib/iota/models/base.rb +13 -0
- data/lib/iota/models/bundle.rb +87 -0
- data/lib/iota/models/input.rb +38 -0
- data/lib/iota/models/seed.rb +33 -0
- data/lib/iota/models/transaction.rb +52 -0
- data/lib/iota/models/transfer.rb +44 -0
- data/lib/iota/multisig/address.rb +41 -0
- data/lib/iota/multisig/multisig.rb +244 -0
- data/lib/iota/utils/ascii.rb +50 -0
- data/lib/iota/utils/broker.rb +124 -0
- data/lib/iota/utils/input_validator.rb +149 -0
- data/lib/iota/utils/object_validator.rb +34 -0
- data/lib/iota/utils/utils.rb +324 -0
- data/lib/iota/version.rb +3 -0
- data/lib/jcurl.jar +0 -0
- data/lib/patch.rb +17 -0
- data/test/ascii_test.rb +114 -0
- data/test/curl_c_test.rb +31 -0
- data/test/curl_java_test.rb +31 -0
- data/test/curl_ruby_test.rb +27 -0
- data/test/kerl_test.rb +52 -0
- data/test/pow_provider_test.rb +36 -0
- data/test/sha3_test.rb +71 -0
- data/test/test_helper.rb +4 -0
- data/test/utils_test.rb +179 -0
- 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
|