iota-ruby 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +12 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/bin/iota-console +15 -0
- data/examples/multisig.rb +57 -0
- data/iota-ruby.gemspec +25 -0
- data/lib/iota/api/api.rb +194 -0
- data/lib/iota/api/commands.rb +99 -0
- data/lib/iota/api/wrappers.rb +402 -0
- data/lib/iota/crypto/bundle.rb +163 -0
- data/lib/iota/crypto/converter.rb +241 -0
- data/lib/iota/crypto/curl.rb +74 -0
- data/lib/iota/crypto/hmac.rb +27 -0
- data/lib/iota/crypto/kerl.rb +78 -0
- data/lib/iota/crypto/private_key.rb +80 -0
- data/lib/iota/crypto/signing.rb +97 -0
- data/lib/iota/models/account.rb +470 -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 +117 -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 +326 -0
- data/lib/iota/version.rb +3 -0
- data/lib/iota.rb +73 -0
- data/test/ascii_test.rb +114 -0
- data/test/kerl_test.rb +64 -0
- data/test/test_helper.rb +4 -0
- data/test/utils_test.rb +173 -0
- metadata +144 -0
@@ -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
|
data/lib/iota/version.rb
ADDED
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
|
data/test/ascii_test.rb
ADDED
@@ -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
|
data/test/test_helper.rb
ADDED