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,470 @@
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)
7
+ @api = client.api
8
+ @validator = client.validator
9
+ @utils = client.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['balances'].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
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['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
+ @api.sendTrytes(trytes, depth, minWeightMagnitude) do |status, data|
267
+ if !status
268
+ raise StandardError, data
269
+ else
270
+ return data
271
+ end
272
+ end
273
+ end
274
+
275
+ def getNewAddress(options = {})
276
+ # default index value
277
+ options = symbolize_keys(options)
278
+ index = options[:index] || 0
279
+
280
+ # validate the index option
281
+ if !@validator.isValue(index) || index < 0
282
+ raise StandardError, "Invalid index provided: #{index}"
283
+ end
284
+
285
+ checksum = options[:checksum] || false
286
+ total = options[:total] || nil
287
+ return_all = options[:returnAll] || false
288
+
289
+ # If no user defined security, use the standard value of 2
290
+ security = options[:security] || 2
291
+
292
+ # validate the security option
293
+ if !@validator.isValue(security) || security < 1 || security > 3
294
+ raise StandardError, "Invalid security provided: #{index}"
295
+ end
296
+
297
+ allAddresses = []
298
+
299
+ # Case 1: total
300
+ #
301
+ # If total number of addresses to generate is supplied, simply generate
302
+ # and return the list of all addresses
303
+ if total
304
+ # Increase index with each iteration
305
+ (0...total).step(1) do |i|
306
+ address = @seed.getAddress(index, security, checksum)
307
+ allAddresses << address
308
+ index += 1
309
+ end
310
+
311
+ return allAddresses
312
+ else
313
+ # Case 2: no total provided
314
+ #
315
+ # Continue calling findTransactions to see if address was already created
316
+ # if null, return list of addresses
317
+ loop do
318
+ newAddress = @seed.getAddress(index, security, checksum)
319
+
320
+ @api.findTransactions(addresses: [newAddress]) do |status, trx_hashes|
321
+ if !status
322
+ raise StandardError, trx_hashes
323
+ end
324
+
325
+ if return_all
326
+ allAddresses << newAddress
327
+ end
328
+
329
+ index += 1
330
+
331
+ return (return_all ? allAddresses : newAddress) if trx_hashes.length == 0
332
+ end
333
+ end
334
+ end
335
+ end
336
+
337
+ private
338
+ def addRemainder(inputs, totalValue, bundle, tag, security, signatureFragments, remainderAddress, hmacKey)
339
+ totalTransferValue = totalValue
340
+ inputs.each do |input|
341
+ balance = input.balance
342
+ timestamp = Time.now.utc.to_i
343
+
344
+ # Add input as bundle entry
345
+ bundle.addEntry(input.security, input.address, -balance, tag, timestamp)
346
+
347
+ # If there is a remainder value
348
+ # Add extra output to send remaining funds to
349
+ if balance >= totalTransferValue
350
+ remainder = balance - totalTransferValue
351
+
352
+ # If user has provided remainder address
353
+ # Use it to send remaining funds to
354
+ if remainder > 0 && remainderAddress
355
+ # Remainder bundle entry
356
+ bundle.addEntry(1, remainderAddress, remainder, tag, timestamp)
357
+
358
+ # Final function for signing inputs
359
+ return signInputs(inputs, bundle, signatureFragments, hmacKey)
360
+ elsif remainder > 0
361
+ # Generate a new Address by calling getNewAddress
362
+ address = getNewAddress({security: security})
363
+ timestamp = Time.now.utc.to_i
364
+
365
+ # Remainder bundle entry
366
+ bundle.addEntry(1, address, remainder, tag, timestamp)
367
+
368
+ # Final function for signing inputs
369
+ return signInputs(inputs, bundle, signatureFragments, hmacKey)
370
+ else
371
+ # If there is no remainder, do not add transaction to bundle simply sign and return
372
+ return signInputs(inputs, bundle, signatureFragments, hmacKey)
373
+ end
374
+ else
375
+ # If multiple inputs provided, subtract the totalTransferValue by the inputs balance
376
+ totalTransferValue -= balance
377
+ end
378
+ end
379
+ end
380
+
381
+ def signInputs(inputs, bundle, signatureFragments, hmacKey)
382
+ bundle.finalize()
383
+ bundle.addTrytes(signatureFragments)
384
+
385
+ # SIGNING OF INPUTS
386
+ # Here we do the actual signing of the inputs
387
+ # Iterate over all bundle transactions, find the inputs
388
+ # Get the corresponding private key and calculate the signatureFragment
389
+
390
+ (0...bundle.bundle.length).step(1) do |i|
391
+ trx = bundle.bundle[i]
392
+
393
+ if trx.value < 0
394
+ address = trx.address
395
+
396
+ # Get the corresponding keyIndex and security of the address
397
+ keyIndex = nil
398
+ keySecurity = nil
399
+ inputs.each do |input|
400
+ if input.address == address
401
+ keyIndex = input.keyIndex
402
+ keySecurity = input.security ? input.security : security
403
+ break
404
+ end
405
+ end
406
+
407
+ # Get corresponding private key of address
408
+ privateKey = IOTA::Crypto::PrivateKey.new(@seed.as_trits, keyIndex, keySecurity)
409
+ key = privateKey.key
410
+
411
+ # Get the normalized bundle hash
412
+ normalizedBundleHash = bundle.normalizedBundle(trx.bundle)
413
+ normalizedBundleFragments = []
414
+
415
+ # Split hash into 3 fragments
416
+ (0...3).step(1) do |l|
417
+ normalizedBundleFragments[l] = normalizedBundleHash.slice(l * 27, 27)
418
+ end
419
+
420
+ # First 6561 trits for the firstFragment
421
+ firstFragment = key[0...6561]
422
+
423
+ # First bundle fragment uses the first 27 trytes
424
+ firstBundleFragment = normalizedBundleFragments[0]
425
+
426
+ # Calculate the new signatureFragment with the first bundle fragment
427
+ firstSignedFragment = IOTA::Crypto::Signing.signatureFragment(firstBundleFragment, firstFragment)
428
+
429
+ # Convert signature to trytes and assign the new signatureFragment
430
+ trx.signatureMessageFragment = IOTA::Crypto::Converter.trytes(firstSignedFragment)
431
+
432
+ # if user chooses higher than 27-tryte security
433
+ # for each security level, add an additional signature
434
+ (1...keySecurity).step(1) do |j|
435
+ # Because the signature is > 2187 trytes, we need to
436
+ # find the subsequent transaction to add the remainder of the signature
437
+ # Same address as well as value = 0 (as we already spent the input)
438
+ if bundle.bundle[i + j].address == address && bundle.bundle[i + j].value == 0
439
+ # Use the next 6561 trits
440
+ nextFragment = key.slice(6561 * j, 6561)
441
+
442
+ nextBundleFragment = normalizedBundleFragments[j]
443
+
444
+ # Calculate the new signature
445
+ nextSignedFragment = IOTA::Crypto::Signing.signatureFragment(nextBundleFragment, nextFragment)
446
+
447
+ # Convert signature to trytes and assign it again to this bundle entry
448
+ bundle.bundle[i + j].signatureMessageFragment = IOTA::Crypto::Converter.trytes(nextSignedFragment)
449
+ end
450
+ end
451
+ end
452
+ end
453
+
454
+ if hmacKey
455
+ hmac = IOTA::Crypto::Hmac(hmacKey)
456
+ hmac.addHMAC(bundle)
457
+ end
458
+
459
+ bundleTrytes = []
460
+
461
+ # Convert all bundle entries into trytes
462
+ bundle.bundle.each do |bndl|
463
+ bundleTrytes << @utils.transactionTrytes(bndl)
464
+ end
465
+
466
+ bundleTrytes.reverse
467
+ end
468
+ end
469
+ end
470
+ end
@@ -0,0 +1,13 @@
1
+ module IOTA
2
+ module Models
3
+ class Base
4
+ def inspect
5
+ self.to_s
6
+ end
7
+
8
+ def symbolize_keys(hash)
9
+ hash.inject({}){ |h,(k,v)| h[k.to_sym] = v; h }
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,87 @@
1
+ module IOTA
2
+ module Models
3
+ class Bundle < Base
4
+ attr_reader :transactions, :persistence, :attachmentTimestamp
5
+
6
+ def initialize(transactions)
7
+ if transactions.class != Array
8
+ raise StandardError, "Invalid transactions array"
9
+ end
10
+
11
+ @transactions = []
12
+ transactions.each do |trx|
13
+ trx = Transaction.new(trx) if trx.class != IOTA::Models::Transaction
14
+ @transactions << trx
15
+ end
16
+
17
+ @persistence = @transactions.first.persistence
18
+ @attachmentTimestamp = @transactions.first.attachmentTimestamp
19
+ end
20
+
21
+ def extractJSON
22
+ utils = IOTA::Utils::Utils.new
23
+
24
+ # Sanity check: if the first tryte pair is not opening bracket, it's not a message
25
+ firstTrytePair = transactions[0].signatureMessageFragment[0] + transactions[0].signatureMessageFragment[1]
26
+
27
+ return nil if firstTrytePair != "OD"
28
+
29
+ index = 0
30
+ notEnded = true
31
+ trytesChunk = ''
32
+ trytesChecked = 0
33
+ preliminaryStop = false
34
+ finalJson = ''
35
+
36
+ while index < transactions.length && notEnded
37
+ messageChunk = transactions[index].signatureMessageFragment
38
+
39
+ # We iterate over the message chunk, reading 9 trytes at a time
40
+ (0...messageChunk.length).step(9) do |i|
41
+ # get 9 trytes
42
+ trytes = messageChunk.slice(i, 9)
43
+ trytesChunk += trytes
44
+
45
+ # Get the upper limit of the tytes that need to be checked
46
+ # because we only check 2 trytes at a time, there is sometimes a leftover
47
+ upperLimit = trytesChunk.length - trytesChunk.length % 2
48
+
49
+ trytesToCheck = trytesChunk[trytesChecked...upperLimit]
50
+
51
+ # We read 2 trytes at a time and check if it equals the closing bracket character
52
+ (0...trytesToCheck.length).step(2) do |j|
53
+ trytePair = trytesToCheck[j] + trytesToCheck[j + 1]
54
+
55
+ # If closing bracket char was found, and there are only trailing 9's
56
+ # we quit and remove the 9's from the trytesChunk.
57
+ if preliminaryStop && trytePair == '99'
58
+ notEnded = false
59
+ break
60
+ end
61
+
62
+ finalJson += utils.fromTrytes(trytePair)
63
+
64
+ # If tryte pair equals closing bracket char, we set a preliminary stop
65
+ # the preliminaryStop is useful when we have a nested JSON object
66
+ if trytePair === "QD"
67
+ preliminaryStop = true
68
+ end
69
+ end
70
+
71
+ break if !notEnded
72
+
73
+ trytesChecked += trytesToCheck.length;
74
+ end
75
+
76
+ # If we have not reached the end of the message yet, we continue with the next transaction in the bundle
77
+ index += 1
78
+ end
79
+
80
+ # If we did not find any JSON, return nil
81
+ return nil if notEnded
82
+
83
+ finalJson
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,38 @@
1
+ module IOTA
2
+ module Models
3
+ class Input < Base
4
+ attr_accessor :address, :keyIndex, :security, :balance
5
+
6
+ def initialize(options)
7
+ utils = IOTA::Utils::Utils.new
8
+ options = symbolize_keys(options)
9
+
10
+ @address = options[:address] || nil
11
+ if @address.nil?
12
+ raise StandardError, "address not provided for transfer"
13
+ end
14
+
15
+ if @address.length == 90 && !utils.isValidChecksum(@address)
16
+ raise StandardError, "Invalid checksum: #{thisTransfer[:address]}"
17
+ end
18
+
19
+ @address = utils.noChecksum(@address) if @address.length == 90
20
+
21
+ @keyIndex = options[:keyIndex]
22
+ @security = options[:security]
23
+ @balance = options[:balance]
24
+ end
25
+
26
+ def valid?
27
+ keysToValidate = [
28
+ { key: 'address', validator: :isAddress, args: nil },
29
+ { key: 'security', validator: :isValue, args: nil },
30
+ { key: 'keyIndex', validator: :isValue, args: nil }
31
+ ]
32
+
33
+ validator = IOTA::Utils::ObjectValidator.new(keysToValidate)
34
+ validator.valid?(self)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,33 @@
1
+ module IOTA
2
+ module Models
3
+ class Seed < Base
4
+ def initialize(seed)
5
+ @utils = IOTA::Utils::Utils.new
6
+
7
+ # Check if correct seed
8
+ if seed.class == String && !@utils.validator.isTrytes(seed)
9
+ raise StandardError, "Invalid seed provided"
10
+ end
11
+
12
+ seed += "9" * (81 - seed.length) if seed.length < 81
13
+
14
+ @seed = seed
15
+ end
16
+
17
+ def getAddress(index, security, checksum)
18
+ pk = IOTA::Crypto::PrivateKey.new(self.as_trits, index, security)
19
+ address_trits = IOTA::Crypto::Signing.address(pk.digests)
20
+ address = IOTA::Crypto::Converter.trytes(address_trits)
21
+
22
+ address = @utils.addChecksum(address) if checksum
23
+
24
+ address
25
+ end
26
+
27
+ # Converter methods
28
+ def as_trits
29
+ IOTA::Crypto::Converter.trits(@seed)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,52 @@
1
+ module IOTA
2
+ module Models
3
+ class Transaction < Base
4
+ attr_accessor :hash, :signatureMessageFragment, :address, :value, :obsoleteTag, :timestamp, :currentIndex, :lastIndex, :bundle, :trunkTransaction, :branchTransaction, :tag, :attachmentTimestamp, :attachmentTimestampLowerBound, :attachmentTimestampUpperBound, :nonce, :persistence
5
+
6
+ def initialize(options)
7
+ options = symbolize_keys(options)
8
+ @hash = options[:hash]
9
+ @signatureMessageFragment = options[:signatureMessageFragment]
10
+ @address = options[:address]
11
+ @value = options[:value]
12
+ @obsoleteTag = options[:obsoleteTag]
13
+ @timestamp = options[:timestamp]
14
+ @currentIndex = options[:currentIndex]
15
+ @lastIndex = options[:lastIndex]
16
+ @bundle = options[:bundle]
17
+ @trunkTransaction = options[:trunkTransaction]
18
+ @branchTransaction = options[:branchTransaction]
19
+ @tag = options[:tag]
20
+ @attachmentTimestamp = options[:attachmentTimestamp]
21
+ @attachmentTimestampLowerBound = options[:attachmentTimestampLowerBound]
22
+ @attachmentTimestampUpperBound = options[:attachmentTimestampUpperBound]
23
+ @nonce = options[:nonce]
24
+ @persistence = nil
25
+ end
26
+
27
+ def valid?
28
+ keysToValidate = [
29
+ { key: 'hash', validator: :isHash, args: nil},
30
+ { key: 'signatureMessageFragment', validator: :isTrytes, args: 2187 },
31
+ { key: 'address', validator: :isHash, args: nil },
32
+ { key: 'value', validator: :isValue, args: nil },
33
+ { key: 'obsoleteTag', validator: :isTrytes, args: 27 },
34
+ { key: 'timestamp', validator: :isValue, args: nil },
35
+ { key: 'currentIndex', validator: :isValue, args: nil },
36
+ { key: 'lastIndex', validator: :isValue, args: nil },
37
+ { key: 'bundle', validator: :isHash, args: nil },
38
+ { key: 'trunkTransaction', validator: :isHash, args: nil },
39
+ { key: 'branchTransaction', validator: :isHash, args: nil },
40
+ { key: 'tag', validator: :isTrytes, args: 27 },
41
+ { key: 'attachmentTimestamp', validator: :isValue, args: nil },
42
+ { key: 'attachmentTimestampLowerBound', validator: :isValue, args: nil },
43
+ { key: 'attachmentTimestampUpperBound', validator: :isValue, args: nil },
44
+ { key: 'nonce', validator: :isTrytes, args: 27 }
45
+ ]
46
+
47
+ validator = IOTA::Utils::ObjectValidator.new(keysToValidate)
48
+ validator.valid?(self)
49
+ end
50
+ end
51
+ end
52
+ end