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,326 @@
1
+ require 'bigdecimal'
2
+
3
+ module IOTA
4
+ module Utils
5
+ class Utils
6
+ include Ascii
7
+
8
+ attr_reader :validator
9
+
10
+ UNIT_MAP = {
11
+ 'i' => 1,
12
+ 'Ki' => 1_000,
13
+ 'Mi' => 1_000_000,
14
+ 'Gi' => 1_000_000_000,
15
+ 'Ti' => 1_000_000_000_000,
16
+ 'Pi' => 1_000_000_000_000_000
17
+ }
18
+
19
+ UNIT_MAP_ORDER = ['Pi', 'Ti', 'Gi', 'Mi', 'Ki', 'i']
20
+
21
+ def initialize
22
+ @validator = InputValidator.new
23
+ end
24
+
25
+ def convertUnits(value, fromUnit, toUnit)
26
+ # Check if wrong unit provided
27
+ if !UNIT_MAP[fromUnit] || !UNIT_MAP[toUnit]
28
+ raise ArgumentError, "Invalid unit provided"
29
+ end
30
+
31
+ # If not valid value, throw error
32
+ if !@validator.isNum(value)
33
+ raise ArgumentError, "Invalid value"
34
+ end
35
+
36
+ converted = (BigDecimal(value.to_s) * UNIT_MAP[fromUnit]) / UNIT_MAP[toUnit]
37
+ converted.to_f
38
+ end
39
+
40
+ def noChecksum(address)
41
+ isSingleAddress = @validator.isString(address)
42
+
43
+ return address if isSingleAddress && address.length == 81
44
+
45
+ # If only single address, turn it into an array
46
+ if isSingleAddress
47
+ address = [address]
48
+ end
49
+
50
+ addressesWithChecksum = []
51
+
52
+ address.each do |addr|
53
+ addressesWithChecksum << addr.slice(0, 81)
54
+ end
55
+
56
+ # return either string or the list
57
+ if isSingleAddress
58
+ return addressesWithChecksum.first
59
+ else
60
+ return addressesWithChecksum
61
+ end
62
+ end
63
+
64
+ def transactionObject(trytes)
65
+ return if !trytes
66
+
67
+ # validity check
68
+ (2279...2295).step(1) do |i|
69
+ raise ArgumentError, "Invalid trytes provided" if trytes[i] != "9"
70
+ end
71
+
72
+ trx = {}
73
+ transactionTrits = IOTA::Crypto::Converter.trits(trytes)
74
+ hash = []
75
+
76
+ curl = IOTA::Crypto::Curl.new
77
+
78
+ # generate the correct transaction hash
79
+ curl.setup()
80
+ curl.absorb(transactionTrits, 0, transactionTrits.length)
81
+ curl.squeeze(hash, 0, 243)
82
+
83
+ trx['hash'] = IOTA::Crypto::Converter.trytes(hash)
84
+ trx['signatureMessageFragment'] = trytes.slice(0, 2187)
85
+ trx['address'] = trytes.slice(2187, 81)
86
+ trx['value'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6804, 33))
87
+ trx['obsoleteTag'] = trytes.slice(2295, 27)
88
+ trx['timestamp'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6966, 27))
89
+ trx['currentIndex'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6993, 27))
90
+ trx['lastIndex'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7020, 27))
91
+ trx['bundle'] = trytes.slice(2349, 81)
92
+ trx['trunkTransaction'] = trytes.slice(2430, 81)
93
+ trx['branchTransaction'] = trytes.slice(2511, 81)
94
+
95
+ trx['tag'] = trytes.slice(2592, 27)
96
+ trx['attachmentTimestamp'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7857, 27))
97
+ trx['attachmentTimestampLowerBound'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7884, 27))
98
+ trx['attachmentTimestampUpperBound'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7911, 27))
99
+ trx['nonce'] = trytes.slice(2646, 27)
100
+
101
+ IOTA::Models::Transaction.new(trx)
102
+ end
103
+
104
+ def transactionTrytes(transaction)
105
+ valueTrits = IOTA::Crypto::Converter.trits(transaction.value)
106
+ valueTrits = valueTrits.concat([0]*(81-valueTrits.length)) if valueTrits.length < 81
107
+
108
+ timestampTrits = IOTA::Crypto::Converter.trits(transaction.timestamp)
109
+ timestampTrits = timestampTrits.concat([0]*(27-timestampTrits.length)) if timestampTrits.length < 27
110
+
111
+ currentIndexTrits = IOTA::Crypto::Converter.trits(transaction.currentIndex)
112
+ currentIndexTrits = currentIndexTrits.concat([0]*(27-currentIndexTrits.length)) if currentIndexTrits.length < 27
113
+
114
+ lastIndexTrits = IOTA::Crypto::Converter.trits(transaction.lastIndex)
115
+ lastIndexTrits = lastIndexTrits.concat([0]*(27-lastIndexTrits.length)) if lastIndexTrits.length < 27
116
+
117
+ attachmentTimestampTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestamp || 0);
118
+ attachmentTimestampTrits = attachmentTimestampTrits.concat([0]*(27-attachmentTimestampTrits.length)) if attachmentTimestampTrits.length < 27
119
+
120
+ attachmentTimestampLowerBoundTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestampLowerBound || 0);
121
+ attachmentTimestampLowerBoundTrits = attachmentTimestampLowerBoundTrits.concat([0]*(27-attachmentTimestampLowerBoundTrits.length)) if attachmentTimestampLowerBoundTrits.length < 27
122
+
123
+ attachmentTimestampUpperBoundTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestampUpperBound || 0);
124
+ attachmentTimestampUpperBoundTrits = attachmentTimestampUpperBoundTrits.concat([0]*(27-attachmentTimestampUpperBoundTrits.length)) if attachmentTimestampUpperBoundTrits.length < 27
125
+
126
+ tag = transaction.tag || transaction.obsoleteTag
127
+
128
+ return (
129
+ transaction.signatureMessageFragment +
130
+ transaction.address +
131
+ IOTA::Crypto::Converter.trytes(valueTrits) +
132
+ transaction.obsoleteTag +
133
+ IOTA::Crypto::Converter.trytes(timestampTrits) +
134
+ IOTA::Crypto::Converter.trytes(currentIndexTrits) +
135
+ IOTA::Crypto::Converter.trytes(lastIndexTrits) +
136
+ transaction.bundle +
137
+ transaction.trunkTransaction +
138
+ transaction.branchTransaction +
139
+ tag +
140
+ IOTA::Crypto::Converter.trytes(attachmentTimestampTrits) +
141
+ IOTA::Crypto::Converter.trytes(attachmentTimestampLowerBoundTrits) +
142
+ IOTA::Crypto::Converter.trytes(attachmentTimestampUpperBoundTrits) +
143
+ transaction.nonce
144
+ )
145
+ end
146
+
147
+ def addChecksum(input, checksumLength = 9, isAddress = true)
148
+ # the length of the trytes to be validated
149
+ validationLength = isAddress ? 81 : nil
150
+
151
+ isSingleInput = @validator.isString(input)
152
+
153
+ # If only single address, turn it into an array
154
+ input = [input] if isSingleInput
155
+
156
+ inputsWithChecksum = input.map do |inputValue|
157
+ # check if correct trytes
158
+ if !@validator.isTrytes(inputValue, validationLength)
159
+ throw Error, "Invalid input provided"
160
+ end
161
+
162
+ kerl = IOTA::Crypto::Kerl.new
163
+
164
+ # Address trits
165
+ addressTrits = IOTA::Crypto::Converter.trits(inputValue)
166
+
167
+ # Checksum trits
168
+ checksumTrits = []
169
+
170
+ # Absorb address trits
171
+ kerl.absorb(addressTrits, 0, addressTrits.length)
172
+
173
+ # Squeeze checksum trits
174
+ kerl.squeeze(checksumTrits, 0, IOTA::Crypto::Curl::HASH_LENGTH)
175
+
176
+ # First 9 trytes as checksum
177
+ checksum = IOTA::Crypto::Converter.trytes(checksumTrits)[81-checksumLength...81]
178
+ inputValue + checksum
179
+ end
180
+
181
+ isSingleInput ? inputsWithChecksum[0] : inputsWithChecksum
182
+ end
183
+
184
+ def isBundle(bundle)
185
+ # If not correct bundle
186
+ return false if !@validator.isArrayOfTxObjects(bundle)
187
+
188
+ bundle = bundle.transactions if bundle.class == IOTA::Models::Bundle
189
+
190
+ totalSum = 0
191
+ bundleHash = bundle[0].bundle
192
+
193
+ # Prepare to absorb txs and get bundleHash
194
+ bundleFromTxs = []
195
+
196
+ kerl = IOTA::Crypto::Kerl.new
197
+
198
+ # Prepare for signature validation
199
+ signaturesToValidate = []
200
+
201
+ bundle.each_with_index do |bundleTx, index|
202
+ totalSum += bundleTx.value
203
+
204
+ # currentIndex has to be equal to the index in the array
205
+ return false if bundleTx.currentIndex != index
206
+
207
+ # Get the transaction trytes
208
+ trytes = transactionTrytes(bundleTx)
209
+
210
+ # Absorb bundle hash + value + timestamp + lastIndex + currentIndex trytes.
211
+ trits = IOTA::Crypto::Converter.trits(trytes.slice(2187, 162))
212
+ kerl.absorb(trits, 0, trits.length)
213
+
214
+ # Check if input transaction
215
+ if bundleTx.value < 0
216
+ address = bundleTx.address
217
+
218
+ newSignatureToValidate = {
219
+ address: address,
220
+ signatureFragments: [bundleTx.signatureMessageFragment]
221
+ }
222
+
223
+ # Find the subsequent txs with the remaining signature fragment
224
+ (index...bundle.length-1).step(1) do |i|
225
+ newBundleTx = bundle[i+1]
226
+
227
+ if newBundleTx.address == address && newBundleTx.value == 0
228
+ newSignatureToValidate[:signatureFragments] << newBundleTx.signatureMessageFragment
229
+ end
230
+ end
231
+
232
+ signaturesToValidate << newSignatureToValidate
233
+ end
234
+ end
235
+
236
+ # Check for total sum, if not equal 0 return error
237
+ return false if totalSum != 0
238
+
239
+ # get the bundle hash from the bundle transactions
240
+ kerl.squeeze(bundleFromTxs, 0, IOTA::Crypto::Kerl::HASH_LENGTH)
241
+ bundleFromTxs = IOTA::Crypto::Converter.trytes(bundleFromTxs)
242
+
243
+ # Check if bundle hash is the same as returned by tx object
244
+ return false if bundleFromTxs != bundleHash
245
+
246
+ # Last tx in the bundle should have currentIndex === lastIndex
247
+ return false if bundle[bundle.length - 1].currentIndex != bundle[bundle.length - 1].lastIndex
248
+
249
+ # Validate the signatures
250
+ (0...signaturesToValidate.length).step(1) do |i|
251
+ return false if !IOTA::Crypto::Signing.validateSignatures(signaturesToValidate[i][:address], signaturesToValidate[i][:signatureFragments], bundleHash)
252
+ end
253
+
254
+ true
255
+ end
256
+
257
+ def isValidChecksum(addressWithChecksum)
258
+ withoutChecksum = noChecksum(addressWithChecksum)
259
+ newWithCheckcum = addChecksum(withoutChecksum)
260
+ newWithCheckcum == addressWithChecksum
261
+ end
262
+
263
+ def validateSignatures(signedBundle, inputAddress)
264
+ bundleHash = nil
265
+ signatureFragments = []
266
+
267
+ signedBundle = signedBundle.transactions if signedBundle.class == IOTA::Models::Bundle
268
+
269
+ (0...signedBundle.length).step(1) do |i|
270
+ if signedBundle[i].address === inputAddress
271
+ bundleHash = signedBundle[i].bundle
272
+
273
+ signature = signedBundle[i].signatureMessageFragment
274
+
275
+ # if we reached remainder bundle
276
+ break if @validator.isString(signature) && @validator.isAllNine(signature)
277
+
278
+ signatureFragments << signature
279
+ end
280
+ end
281
+
282
+ return false if bundleHash.nil?
283
+
284
+ IOTA::Crypto::Signing.validateSignatures(inputAddress, signatureFragments, bundleHash)
285
+ end
286
+
287
+ def categorizeTransfers(transfers, addresses)
288
+ categorized = {
289
+ sent: [],
290
+ received: []
291
+ }
292
+
293
+ addresses = addresses.map { |a| a[0...81] }
294
+
295
+ # Iterate over all bundles and sort them between incoming and outgoing transfers
296
+ transfers.each do |bundle|
297
+ spentAlreadyAdded = false
298
+
299
+ bundle = IOTA::Models::Bundle.new(bundle) if bundle.class != IOTA::Models::Bundle
300
+
301
+ # Iterate over every bundle entry
302
+ bundle.transactions.each_with_index do |bundleEntry, bundleIndex|
303
+ address = bundleEntry.address[0...81]
304
+ if !addresses.index(address).nil?
305
+ # Check if it's a remainder address
306
+ isRemainder = (bundleEntry.currentIndex == bundleEntry.lastIndex) && bundleEntry.lastIndex != 0
307
+
308
+ # check if sent transaction
309
+ if bundleEntry.value < 0 && !spentAlreadyAdded && !isRemainder
310
+ categorized[:sent] << bundle
311
+
312
+ # too make sure we do not add transactions twice
313
+ spentAlreadyAdded = true
314
+ elsif bundleEntry.value >= 0 && !spentAlreadyAdded && !isRemainder
315
+ # check if received transaction, or 0 value (message)
316
+ # also make sure that this is not a 2nd tx for spent inputs
317
+ categorized[:received] << bundle
318
+ end
319
+ end
320
+ end
321
+ end
322
+ categorized
323
+ end
324
+ end
325
+ end
326
+ end
@@ -0,0 +1,3 @@
1
+ module IOTA
2
+ VERSION = "1.0.1"
3
+ end
data/lib/iota.rb ADDED
@@ -0,0 +1,73 @@
1
+ require "iota/version"
2
+
3
+ require "iota/utils/input_validator"
4
+ require "iota/utils/object_validator"
5
+ require "iota/utils/ascii"
6
+ require "iota/utils/utils"
7
+ require "iota/utils/broker"
8
+
9
+ require "iota/api/commands"
10
+ require "iota/api/wrappers"
11
+ require "iota/api/api"
12
+
13
+ require "iota/crypto/curl"
14
+ require "iota/crypto/kerl"
15
+ require "iota/crypto/converter"
16
+ require "iota/crypto/bundle"
17
+ require "iota/crypto/signing"
18
+ require "iota/crypto/hmac"
19
+ require "iota/crypto/private_key"
20
+
21
+ require "iota/multisig/address"
22
+ require "iota/multisig/multisig"
23
+
24
+ require "iota/models/base"
25
+ require "iota/models/input"
26
+ require "iota/models/transfer"
27
+ require "iota/models/seed"
28
+ require "iota/models/transaction"
29
+ require "iota/models/bundle"
30
+ require "iota/models/account"
31
+
32
+ module IOTA
33
+ class Client
34
+ attr_reader :version, :host, :port, :provider, :sandbox, :token, :broker, :api, :utils, :validator, :multisig
35
+
36
+ def initialize(settings = {})
37
+ setSettings(settings)
38
+ @utils = IOTA::Utils::Utils.new
39
+ @validator = @utils.validator
40
+ @multisig = IOTA::Multisig::Multisig.new(self)
41
+ # TODO: Implement MAM
42
+ # TODO: Implement Flash Channel
43
+ end
44
+
45
+ def changeNode(settings = {})
46
+ setSettings(settings)
47
+ self
48
+ end
49
+
50
+ private
51
+ def setSettings(settings)
52
+ settings = symbolize_keys(settings)
53
+ @host = settings[:host] ? settings[:host] : "http://localhost"
54
+ @port = settings[:port] ? settings[:port] : 14265
55
+ @provider = settings[:provider] || @host.gsub(/\/$/, '') + ":" + @port.to_s
56
+ @sandbox = settings[:sandbox] || false
57
+ @token = settings[:token] || false
58
+ @timeout = settings[:timeout] || 120
59
+
60
+ if @sandbox
61
+ @sandbox = @provider.gsub(/\/$/, '')
62
+ @provider = @sandbox + '/commands'
63
+ end
64
+
65
+ @broker = IOTA::Utils::Broker.new(@provider, @token, @timeout)
66
+ @api = IOTA::API::Api.new(@broker, @sandbox)
67
+ end
68
+
69
+ def symbolize_keys(hash)
70
+ hash.inject({}){ |h,(k,v)| h[k.to_sym] = v; h }
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,114 @@
1
+ require "test_helper"
2
+
3
+ class AsciiTest < Minitest::Test
4
+ def setup
5
+ @utils = IOTA::Utils::Utils.new
6
+ end
7
+
8
+ def test_that_from_tryte_works
9
+ tests = [
10
+ {
11
+ message: " ASDFDSAFDSAja9fd",
12
+ expected: true
13
+ },
14
+ {
15
+ message: "994239432",
16
+ expected: true
17
+ },
18
+ {
19
+ message: "{ 'a' : 'b', 'c': 'd', 'e': '#asdfd?$' }",
20
+ expected: true
21
+ },
22
+ {
23
+ message: "{ 'a' : 'b', 'c': {'nested': 'json', 'much': 'wow', 'array': [ true, false, 'yes' ] } }",
24
+ expected: true
25
+ },
26
+ {
27
+ message: 994239432,
28
+ expected: false
29
+ },
30
+ {
31
+ message: 'true',
32
+ expected: true
33
+ },
34
+ {
35
+ message: [9, 'yes', true],
36
+ expected: false
37
+ },
38
+ {
39
+ message: { 'a' => 'b' },
40
+ expected: false
41
+ }
42
+ ]
43
+
44
+ tests.each do |test|
45
+ trytes = @utils.toTrytes(test[:message])
46
+ str = @utils.fromTrytes(trytes)
47
+
48
+ if test[:expected]
49
+ assert str.eql?(test[:message])
50
+ else
51
+ assert_nil str
52
+ end
53
+ end
54
+ end
55
+
56
+ def test_that_to_trytes_works
57
+ tests = [
58
+ {
59
+ message: " ΣASDFDSAFDSAja9fd",
60
+ expected: false
61
+ },
62
+ {
63
+ message: " ASDFDSAFDSAja9fd",
64
+ expected: true
65
+ },
66
+ {
67
+ message: "994239432",
68
+ expected: true
69
+ },
70
+ {
71
+ message: "{ 'a' : 'b', 'c': 'd', 'e': '#asdfd?$' }",
72
+ expected: true
73
+ },
74
+ {
75
+ message: "{ 'a' : 'b', 'c': {'nested': 'json', 'much': 'wow', 'array': [ true, false, 'yes' ] } }",
76
+ expected: true
77
+ },
78
+ {
79
+ message: "{ 'a' : 'b', 'c': {'nested': 'json', 'much': 'wow', 'array': [ true, false, 'yes' ] } }",
80
+ expected: true
81
+ },
82
+ {
83
+ message: "{'message': 'IOTA is a revolutionary new transactional settlement and data transfer layer for the Internet of Things. It's based on a new distributed ledger, the Tangle, which overcomes the inefficiencies of current Blockchain designs and introduces a new way of reaching consensus in a decentralized peer-to-peer system. For the first time ever, through IOTA people can transfer money without any fees. This means that even infinitesimally small nanopayments can be made through IOTA. IOTA is the missing puzzle piece for the Machine Economy to fully emerge and reach its desired potential. We envision IOTA to be the public, permissionless backbone for the Internet of Things that enables true interoperability between all devices. Tangle: A directed acyclic graph (DAG) as a distributed ledger which stores all transaction data of the IOTA network. It is a Blockchain without the blocks and the chain (so is it really a Blockchain?). The Tangle is the first distributed ledger to achieve scalability, no fee transactions, data integrity and transmission as well as quantum-computing protection. Contrary to today's Blockchains, consensus is no-longer decoupled but instead an intrinsic part of the system, leading to a completely decentralized and self-regulating peer-to-peer network. All IOTA\'s which will ever exist have been created with the genesis transaction. This means that the total supply of IOTA\'s will always stay the same and you cannot mine IOTA\'s. Therefore keep in mind, if you do Proof of Work in IOTA you are not generating new IOTA tokens, you\'re simply verifying other transactions.'}",
84
+ expected: true
85
+ },
86
+ {
87
+ message: 994239432,
88
+ expected: false
89
+ },
90
+ {
91
+ message: true,
92
+ expected: false
93
+ },
94
+ {
95
+ message: [9, 'yes', true],
96
+ expected: false
97
+ },
98
+ {
99
+ message: { 'a' => 'b' },
100
+ expected: false
101
+ }
102
+ ]
103
+
104
+ tests.each do |test|
105
+ trytes = @utils.toTrytes(test[:message])
106
+
107
+ if test[:expected]
108
+ refute_nil trytes
109
+ else
110
+ assert_nil trytes
111
+ end
112
+ end
113
+ end
114
+ end
data/test/kerl_test.rb ADDED
@@ -0,0 +1,64 @@
1
+ require "test_helper"
2
+
3
+ class KerlTest < Minitest::Test
4
+ def setup
5
+ @converter = IOTA::Crypto::Converter
6
+ @kerl = IOTA::Crypto::Kerl.new
7
+ end
8
+
9
+ def test_that_sha3_works
10
+ digest = "\xe0\xef\x02\xd2FD\xa7\xb2\x8b<\x1b\x01\xc4\xfe\x13zI\x86M[\xdefV\xfa\xf49\xe1\xeb\xa6X\x06M\x9e\xcf\x842U\xba\x90=\x1c\xeb\xdcf\xff/\x16\xce".unpack('C*')
11
+ hexdigest = "e0ef02d24644a7b28b3c1b01c4fe137a49864d5bde6656faf439e1eba658064d9ecf843255ba903d1cebdc66ff2f16ce"
12
+
13
+ a = Digest::SHA3.new(384)
14
+ a.update("GYOMKVTSNHVJNCNFBBAH9AAMXLPLLLROQY99QN9DLSJUHDPBLCFFAIQXZA9BKMBJCYSFHFPXAHDWZFEIZ")
15
+
16
+ assert a.digest_length == 48
17
+ assert a.digest.bytes == digest
18
+ assert a.hexdigest == hexdigest
19
+ end
20
+
21
+ def test_that_absorb_squeeze_works
22
+ input = "GYOMKVTSNHVJNCNFBBAH9AAMXLPLLLROQY99QN9DLSJUHDPBLCFFAIQXZA9BKMBJCYSFHFPXAHDWZFEIZ"
23
+ expected = "OXJCNFHUNAHWDLKKPELTBFUCVW9KLXKOGWERKTJXQMXTKFKNWNNXYD9DMJJABSEIONOSJTTEVKVDQEWTW"
24
+ trits = @converter.trits(input)
25
+
26
+ @kerl.reset
27
+ @kerl.absorb(trits)
28
+ hashTrits = []
29
+ @kerl.squeeze(hashTrits)
30
+ hash = @converter.trytes(hashTrits)
31
+
32
+ assert expected == hash
33
+ end
34
+
35
+ def test_that_absorb_multi_squeeze_works
36
+ input = "9MIDYNHBWMBCXVDEFOFWINXTERALUKYYPPHKP9JJFGJEIUY9MUDVNFZHMMWZUYUSWAIOWEVTHNWMHANBH"
37
+ expected = "G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJBVBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ9IFGYOLZXWZBKWZN9QOOTBQMWMUBLEWUEEASRHRTNIQWJQNDWRYLCA"
38
+
39
+ trits = @converter.trits(input)
40
+ @kerl.reset
41
+ @kerl.absorb(trits)
42
+
43
+ hashTrits = []
44
+ @kerl.squeeze(hashTrits, 0, IOTA::Crypto::Curl::HASH_LENGTH * 2)
45
+ hash = @converter.trytes(hashTrits)
46
+
47
+ assert expected == hash
48
+ end
49
+
50
+ def test_that_multi_absorb_multi_squeeze_works
51
+ input = "G9JYBOMPUXHYHKSNRNMMSSZCSHOFYOYNZRSZMAAYWDYEIMVVOGKPJBVBM9TDPULSFUNMTVXRKFIDOHUXXVYDLFSZYZTWQYTE9SPYYWYTXJYQ9IFGYOLZXWZBKWZN9QOOTBQMWMUBLEWUEEASRHRTNIQWJQNDWRYLCA"
52
+ expected = "LUCKQVACOGBFYSPPVSSOXJEKNSQQRQKPZC9NXFSMQNRQCGGUL9OHVVKBDSKEQEBKXRNUJSRXYVHJTXBPDWQGNSCDCBAIRHAQCOWZEBSNHIJIGPZQITIBJQ9LNTDIBTCQ9EUWKHFLGFUVGGUWJONK9GBCDUIMAYMMQX"
53
+
54
+ trits = @converter.trits(input)
55
+ @kerl.reset
56
+ @kerl.absorb(trits, 0, trits.length)
57
+
58
+ hashTrits = []
59
+ @kerl.squeeze(hashTrits, 0, IOTA::Crypto::Curl::HASH_LENGTH * 2)
60
+ hash = @converter.trytes(hashTrits)
61
+
62
+ assert expected == hash
63
+ end
64
+ end
@@ -0,0 +1,4 @@
1
+ $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__)
2
+ require "iota"
3
+
4
+ require "minitest/autorun"