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.
Files changed (64) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +8 -0
  3. data/.gitignore +15 -0
  4. data/.travis.yml +24 -0
  5. data/.yardopts +7 -0
  6. data/CHANGELOG.md +18 -0
  7. data/Gemfile +3 -0
  8. data/LICENSE +21 -0
  9. data/README.md +121 -0
  10. data/Rakefile +36 -0
  11. data/bin/iota-console +15 -0
  12. data/examples/multisig.rb +69 -0
  13. data/ext/ccurl/ccurl.c +134 -0
  14. data/ext/ccurl/extconf.rb +22 -0
  15. data/ext/jcurl/JCurl.java +126 -0
  16. data/ext/jcurl/JCurlService.java +36 -0
  17. data/ext/pow/ccurl-0.3.0.dll +0 -0
  18. data/ext/pow/libccurl-0.3.0.dylib +0 -0
  19. data/ext/pow/libccurl-0.3.0.so +0 -0
  20. data/iota-ruby.gemspec +37 -0
  21. data/lib/iota.rb +76 -0
  22. data/lib/iota/api/api.rb +251 -0
  23. data/lib/iota/api/commands.rb +113 -0
  24. data/lib/iota/api/transport.rb +43 -0
  25. data/lib/iota/api/wrappers.rb +429 -0
  26. data/lib/iota/crypto/bundle.rb +163 -0
  27. data/lib/iota/crypto/converter.rb +244 -0
  28. data/lib/iota/crypto/curl.rb +18 -0
  29. data/lib/iota/crypto/curl_c.rb +17 -0
  30. data/lib/iota/crypto/curl_java.rb +18 -0
  31. data/lib/iota/crypto/curl_ruby.rb +70 -0
  32. data/lib/iota/crypto/hmac.rb +27 -0
  33. data/lib/iota/crypto/kerl.rb +82 -0
  34. data/lib/iota/crypto/pow_provider.rb +27 -0
  35. data/lib/iota/crypto/private_key.rb +80 -0
  36. data/lib/iota/crypto/sha3_ruby.rb +122 -0
  37. data/lib/iota/crypto/signing.rb +97 -0
  38. data/lib/iota/models/account.rb +489 -0
  39. data/lib/iota/models/base.rb +13 -0
  40. data/lib/iota/models/bundle.rb +87 -0
  41. data/lib/iota/models/input.rb +38 -0
  42. data/lib/iota/models/seed.rb +33 -0
  43. data/lib/iota/models/transaction.rb +52 -0
  44. data/lib/iota/models/transfer.rb +44 -0
  45. data/lib/iota/multisig/address.rb +41 -0
  46. data/lib/iota/multisig/multisig.rb +244 -0
  47. data/lib/iota/utils/ascii.rb +50 -0
  48. data/lib/iota/utils/broker.rb +124 -0
  49. data/lib/iota/utils/input_validator.rb +149 -0
  50. data/lib/iota/utils/object_validator.rb +34 -0
  51. data/lib/iota/utils/utils.rb +324 -0
  52. data/lib/iota/version.rb +3 -0
  53. data/lib/jcurl.jar +0 -0
  54. data/lib/patch.rb +17 -0
  55. data/test/ascii_test.rb +114 -0
  56. data/test/curl_c_test.rb +31 -0
  57. data/test/curl_java_test.rb +31 -0
  58. data/test/curl_ruby_test.rb +27 -0
  59. data/test/kerl_test.rb +52 -0
  60. data/test/pow_provider_test.rb +36 -0
  61. data/test/sha3_test.rb +71 -0
  62. data/test/test_helper.rb +4 -0
  63. data/test/utils_test.rb +179 -0
  64. metadata +183 -0
@@ -0,0 +1,50 @@
1
+ module IOTA
2
+ module Utils
3
+ module Ascii
4
+ TRYTE_VALUES = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ"
5
+
6
+ def toTrytes(input)
7
+ # If input is not a string, return nil
8
+ return nil if !@validator.isString(input)
9
+ trytes = ""
10
+
11
+ (0...input.length).step(1) do |i|
12
+ char = input[i]
13
+ asciiValue = char.bytes.sum
14
+
15
+ # If not recognizable ASCII character, return null
16
+ return nil if asciiValue > 255
17
+
18
+ firstValue = asciiValue % 27
19
+ secondValue = (asciiValue - firstValue) / 27
20
+
21
+ trytesValue = TRYTE_VALUES[firstValue] + TRYTE_VALUES[secondValue]
22
+
23
+ trytes += trytesValue
24
+ end
25
+
26
+ trytes
27
+ end
28
+
29
+ def fromTrytes(input)
30
+ # If input is invalid trytes or input length is odd
31
+ return nil if !@validator.isTrytes(input) || input.length % 2 != 0
32
+
33
+ outputString = ""
34
+
35
+ (0...input.length).step(2) do |i|
36
+ trytes = input[i] + input[i + 1]
37
+
38
+ firstValue = TRYTE_VALUES.index(trytes[0])
39
+ secondValue = TRYTE_VALUES.index(trytes[1])
40
+
41
+ decimalValue = firstValue + secondValue * 27
42
+
43
+ outputString += decimalValue.chr
44
+ end
45
+
46
+ outputString
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,124 @@
1
+ require 'net/http'
2
+ require 'uri'
3
+ require 'json'
4
+
5
+ module IOTA
6
+ module Utils
7
+ class Broker
8
+ def initialize(provider, token, timeout = 120, user: nil, password: nil)
9
+ @provider, @token, @user, @password = provider, token, user, password
10
+ @timeout = timeout if timeout.to_i > 0
11
+ end
12
+
13
+ def send(command, &callback)
14
+ uri, request, req_options = prepareRequest(command)
15
+ response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
16
+ http.request(request)
17
+ end
18
+
19
+ success = true
20
+ begin
21
+ data = JSON.parse(response.body)
22
+ if data.key?('error')
23
+ data = data['error']
24
+ success = false
25
+ else
26
+ data = prepareResponse(data, command[:command])
27
+ end
28
+ rescue JSON::ParserError
29
+ success = false
30
+ data = "Invalid response"
31
+ end
32
+ callback ? callback.call(success, data) : [success, data]
33
+ end
34
+
35
+ def sandboxSend(uri, &callback)
36
+ processed = false
37
+ success = false
38
+ retValue = nil
39
+
40
+ loop do
41
+ uri, request, req_options = prepareRequest(nil, uri, Net::HTTP::Get)
42
+
43
+ response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
44
+ http.request(request)
45
+ end
46
+
47
+ begin
48
+ data = JSON.parse(response.body)
49
+ if data['status'] == 'FINISHED'
50
+ processed = true
51
+ success = true
52
+ retValue = data['attachToTangleResponse']['trytes']
53
+ elsif data['status'] == 'FAILED'
54
+ processed = true
55
+ success = false
56
+ retValue = "Sandbox transaction processing failed. Please retry."
57
+ end
58
+ rescue JSON::ParserError
59
+ processed = true
60
+ success = false
61
+ retValue = "Invalid response"
62
+ end
63
+
64
+ break if processed
65
+
66
+ # Sleep for 15 seconds before making another request
67
+ sleep 15
68
+ end
69
+
70
+ callback ? callback.call(success, retValue) : [success, retValue]
71
+ end
72
+
73
+ private
74
+ def prepareRequest(command = nil, uri = @provider, requestMethod = Net::HTTP::Post)
75
+ uri = URI.parse(uri)
76
+ request = requestMethod.new(uri)
77
+ request.content_type = "application/json"
78
+ request['X-IOTA-API-Version'] = '1'
79
+ request.body = JSON.dump(command) if !command.nil?
80
+
81
+ req_options = {
82
+ use_ssl: uri.scheme == "https",
83
+ read_timeout: @timeout || 120
84
+ }
85
+
86
+ if @token
87
+ request["Authorization"] = "token #{@token}"
88
+ elsif @user && @password
89
+ request.basic_auth @user, @password
90
+ end
91
+
92
+ [uri, request, req_options]
93
+ end
94
+
95
+ def prepareResponse(result, command)
96
+ resultMap = {
97
+ 'getNeighbors' => 'neighbors',
98
+ 'addNeighbors' => 'addedNeighbors',
99
+ 'removeNeighbors' => 'removedNeighbors',
100
+ 'getTips' => 'hashes',
101
+ 'findTransactions' => 'hashes',
102
+ 'getTrytes' => 'trytes',
103
+ 'getInclusionStates' => 'states',
104
+ 'attachToTangle' => 'trytes',
105
+ 'checkConsistency' => 'state',
106
+ 'getBalances' => 'balances',
107
+ 'wereAddressesSpentFrom'=> 'states'
108
+ }
109
+
110
+ # If correct result and we want to prepare the result
111
+ if result && resultMap.key?(command)
112
+ # If the response is from the sandbox, don't prepare the result
113
+ if command === 'attachToTangle' && result.key?('id')
114
+ result = result
115
+ else
116
+ result = result[resultMap[command]]
117
+ end
118
+ end
119
+
120
+ result
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,149 @@
1
+ module IOTA
2
+ module Utils
3
+ class InputValidator
4
+ def isAllNine(input)
5
+ /^[9]+$/.match?(input)
6
+ end
7
+
8
+ def isValue(input)
9
+ input && input.is_a?(Integer)
10
+ end
11
+
12
+ def isNum(input)
13
+ /^(\d+\.?\d{0,15}|\.\d{0,15})$/.match?(input.to_s)
14
+ end
15
+
16
+ def isString(input)
17
+ input.class == String
18
+ end
19
+
20
+ def isArray(input)
21
+ input.class == Array
22
+ end
23
+
24
+ def isObject(input)
25
+ input.class == Hash
26
+ end
27
+
28
+ def isHash(input)
29
+ isTrytes(input, 81)
30
+ end
31
+
32
+ def isAddress(input)
33
+ if input.length == 90
34
+ if !isTrytes(input, 90)
35
+ return false
36
+ end
37
+ else
38
+ if !isTrytes(input, 81)
39
+ return false
40
+ end
41
+ end
42
+
43
+ true
44
+ end
45
+
46
+ def isTrytes(input, length = "0,")
47
+ isString(input) && /^[9A-Z]{#{length}}$/.match?(input)
48
+ end
49
+
50
+ def isArrayOfTrytes(trytesArray)
51
+ return false if !isArray(trytesArray)
52
+
53
+ trytesArray.each do |tryte|
54
+ return false if !isTrytes(tryte, 2673)
55
+ end
56
+
57
+ true
58
+ end
59
+
60
+ def isArrayOfHashes(input)
61
+ return false if !isArray(input)
62
+
63
+ input.each do |entry|
64
+ return false if !isAddress(entry)
65
+ end
66
+
67
+ true
68
+ end
69
+
70
+ def isArrayOfAttachedTrytes(trytes)
71
+ return false if !isArray(trytes)
72
+
73
+ (0...trytes.length).step(1) do |i|
74
+ tryte = trytes[i]
75
+ return false if !isTrytes(tryte, 2673)
76
+
77
+ lastTrytes = tryte.slice(2673 - (3 * 81), trytes.length)
78
+ return false if isAllNine(lastTrytes)
79
+ end
80
+
81
+ true
82
+ end
83
+
84
+ def isArrayOfTxObjects(bundle)
85
+ bundle = bundle.transactions if bundle.class == IOTA::Models::Bundle
86
+
87
+ return false if !isArray(bundle) || bundle.length == 0
88
+
89
+ bundle.each do |txObject|
90
+ if txObject.class != IOTA::Models::Transaction
91
+ txObject = IOTA::Models::Transaction.new(txObject)
92
+ end
93
+
94
+ return false if !txObject.valid?
95
+ end
96
+
97
+ true
98
+ end
99
+
100
+ def isTransfersArray(transfersArray)
101
+ return false if !isArray(transfersArray)
102
+
103
+ transfersArray.each do |transfer|
104
+ if transfer.class != IOTA::Models::Transfer
105
+ transfer = IOTA::Models::Transfer.new(transfer)
106
+ end
107
+
108
+ return false if !transfer.valid?
109
+ end
110
+
111
+ true
112
+ end
113
+
114
+ def isInputs(inputs)
115
+ return false if !isArray(inputs)
116
+
117
+ inputs.each do |input|
118
+ if input.class != IOTA::Models::Input
119
+ input = IOTA::Models::Input.new(input)
120
+ end
121
+
122
+ return false if !input.valid?
123
+ end
124
+
125
+ true
126
+ end
127
+
128
+ # Checks that a given uri is valid
129
+ # Valid Examples:
130
+ # udp://[2001:db8:a0b:12f0::1]:14265
131
+ # udp://[2001:db8:a0b:12f0::1]
132
+ # udp://8.8.8.8:14265
133
+ # udp://domain.com
134
+ # udp://domain2.com:14265
135
+ def isUri(node)
136
+ getInside = /^(udp|tcp):\/\/([\[][^\]\.]*[\]]|[^\[\]:]*)[:]{0,1}([0-9]{1,}$|$)/i
137
+
138
+ stripBrackets = /[\[]{0,1}([^\[\]]*)[\]]{0,1}/
139
+
140
+ uriTest = /((^\s*((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))\s*$)|(^\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))(%.+)?\s*$))|(^\s*((?=.{1,255}$)(?=.*[A-Za-z].*)[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|\b-){0,61}[0-9A-Za-z])?)*)\s*$)/
141
+
142
+ match = getInside.match(node)
143
+ return false if match.nil? || match[2].nil?
144
+
145
+ uriTest.match?(stripBrackets.match(match[2])[1])
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,34 @@
1
+ module IOTA
2
+ module Utils
3
+ class ObjectValidator
4
+ def initialize(keysToValidate)
5
+ @validator = InputValidator.new
6
+ @keysToValidate = keysToValidate
7
+ end
8
+
9
+ def valid?(object)
10
+ valid = true
11
+ @keysToValidate.each do |keyToValidate|
12
+ key = keyToValidate[:key]
13
+ func = keyToValidate[:validator]
14
+ args = keyToValidate[:args]
15
+
16
+ # If input does not have keyIndex and address, return false
17
+ if !object.respond_to?(key)
18
+ valid = false
19
+ break
20
+ end
21
+
22
+ # If input function does not return true, exit
23
+ method_args = [object.send(key)]
24
+ method_args << args if !args.nil?
25
+ if !@validator.method(func).call(*method_args)
26
+ valid = false
27
+ break
28
+ end
29
+ end
30
+ valid
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,324 @@
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 nil if !trytes
66
+
67
+ # validity check
68
+ (2279...2295).step(1) do |i|
69
+ return nil if trytes[i] != "9"
70
+ end
71
+
72
+ trx = {}
73
+ transactionTrits = IOTA::Crypto::Converter.trits(trytes)
74
+ hash = []
75
+
76
+ # generate the correct transaction hash
77
+ curl = IOTA::Crypto::Curl.new
78
+ curl.absorb(transactionTrits)
79
+ curl.squeeze(hash)
80
+
81
+ trx['hash'] = IOTA::Crypto::Converter.trytes(hash)
82
+ trx['signatureMessageFragment'] = trytes.slice(0, 2187)
83
+ trx['address'] = trytes.slice(2187, 81)
84
+ trx['value'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6804, 33))
85
+ trx['obsoleteTag'] = trytes.slice(2295, 27)
86
+ trx['timestamp'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6966, 27))
87
+ trx['currentIndex'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6993, 27))
88
+ trx['lastIndex'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7020, 27))
89
+ trx['bundle'] = trytes.slice(2349, 81)
90
+ trx['trunkTransaction'] = trytes.slice(2430, 81)
91
+ trx['branchTransaction'] = trytes.slice(2511, 81)
92
+
93
+ trx['tag'] = trytes.slice(2592, 27)
94
+ trx['attachmentTimestamp'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7857, 27))
95
+ trx['attachmentTimestampLowerBound'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7884, 27))
96
+ trx['attachmentTimestampUpperBound'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7911, 27))
97
+ trx['nonce'] = trytes.slice(2646, 27)
98
+
99
+ IOTA::Models::Transaction.new(trx)
100
+ end
101
+
102
+ def transactionTrytes(transaction)
103
+ valueTrits = IOTA::Crypto::Converter.trits(transaction.value)
104
+ valueTrits = valueTrits.concat([0]*(81-valueTrits.length)) if valueTrits.length < 81
105
+
106
+ timestampTrits = IOTA::Crypto::Converter.trits(transaction.timestamp)
107
+ timestampTrits = timestampTrits.concat([0]*(27-timestampTrits.length)) if timestampTrits.length < 27
108
+
109
+ currentIndexTrits = IOTA::Crypto::Converter.trits(transaction.currentIndex)
110
+ currentIndexTrits = currentIndexTrits.concat([0]*(27-currentIndexTrits.length)) if currentIndexTrits.length < 27
111
+
112
+ lastIndexTrits = IOTA::Crypto::Converter.trits(transaction.lastIndex)
113
+ lastIndexTrits = lastIndexTrits.concat([0]*(27-lastIndexTrits.length)) if lastIndexTrits.length < 27
114
+
115
+ attachmentTimestampTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestamp || 0);
116
+ attachmentTimestampTrits = attachmentTimestampTrits.concat([0]*(27-attachmentTimestampTrits.length)) if attachmentTimestampTrits.length < 27
117
+
118
+ attachmentTimestampLowerBoundTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestampLowerBound || 0);
119
+ attachmentTimestampLowerBoundTrits = attachmentTimestampLowerBoundTrits.concat([0]*(27-attachmentTimestampLowerBoundTrits.length)) if attachmentTimestampLowerBoundTrits.length < 27
120
+
121
+ attachmentTimestampUpperBoundTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestampUpperBound || 0);
122
+ attachmentTimestampUpperBoundTrits = attachmentTimestampUpperBoundTrits.concat([0]*(27-attachmentTimestampUpperBoundTrits.length)) if attachmentTimestampUpperBoundTrits.length < 27
123
+
124
+ tag = transaction.tag || transaction.obsoleteTag
125
+
126
+ return (
127
+ transaction.signatureMessageFragment +
128
+ transaction.address +
129
+ IOTA::Crypto::Converter.trytes(valueTrits) +
130
+ transaction.obsoleteTag +
131
+ IOTA::Crypto::Converter.trytes(timestampTrits) +
132
+ IOTA::Crypto::Converter.trytes(currentIndexTrits) +
133
+ IOTA::Crypto::Converter.trytes(lastIndexTrits) +
134
+ transaction.bundle +
135
+ transaction.trunkTransaction +
136
+ transaction.branchTransaction +
137
+ tag +
138
+ IOTA::Crypto::Converter.trytes(attachmentTimestampTrits) +
139
+ IOTA::Crypto::Converter.trytes(attachmentTimestampLowerBoundTrits) +
140
+ IOTA::Crypto::Converter.trytes(attachmentTimestampUpperBoundTrits) +
141
+ transaction.nonce
142
+ )
143
+ end
144
+
145
+ def addChecksum(input, checksumLength = 9, isAddress = true)
146
+ # the length of the trytes to be validated
147
+ validationLength = isAddress ? 81 : nil
148
+
149
+ isSingleInput = @validator.isString(input)
150
+
151
+ # If only single address, turn it into an array
152
+ input = [input] if isSingleInput
153
+
154
+ inputsWithChecksum = input.map do |inputValue|
155
+ # check if correct trytes
156
+ if !@validator.isTrytes(inputValue, validationLength)
157
+ raise ArgumentError, "Invalid input provided"
158
+ end
159
+
160
+ kerl = IOTA::Crypto::Kerl.new
161
+
162
+ # Address trits
163
+ addressTrits = IOTA::Crypto::Converter.trits(inputValue)
164
+
165
+ # Checksum trits
166
+ checksumTrits = []
167
+
168
+ # Absorb address trits
169
+ kerl.absorb(addressTrits, 0, addressTrits.length)
170
+
171
+ # Squeeze checksum trits
172
+ kerl.squeeze(checksumTrits, 0, IOTA::Crypto::Curl::HASH_LENGTH)
173
+
174
+ # First 9 trytes as checksum
175
+ checksum = IOTA::Crypto::Converter.trytes(checksumTrits)[81-checksumLength...81]
176
+ inputValue + checksum
177
+ end
178
+
179
+ isSingleInput ? inputsWithChecksum[0] : inputsWithChecksum
180
+ end
181
+
182
+ def isBundle(bundle)
183
+ # If not correct bundle
184
+ return false if !@validator.isArrayOfTxObjects(bundle)
185
+
186
+ bundle = bundle.transactions if bundle.class == IOTA::Models::Bundle
187
+
188
+ totalSum = 0
189
+ bundleHash = bundle[0].bundle
190
+
191
+ # Prepare to absorb txs and get bundleHash
192
+ bundleFromTxs = []
193
+
194
+ kerl = IOTA::Crypto::Kerl.new
195
+
196
+ # Prepare for signature validation
197
+ signaturesToValidate = []
198
+
199
+ bundle.each_with_index do |bundleTx, index|
200
+ totalSum += bundleTx.value
201
+
202
+ # currentIndex has to be equal to the index in the array
203
+ return false if bundleTx.currentIndex != index
204
+
205
+ # Get the transaction trytes
206
+ trytes = transactionTrytes(bundleTx)
207
+
208
+ # Absorb bundle hash + value + timestamp + lastIndex + currentIndex trytes.
209
+ trits = IOTA::Crypto::Converter.trits(trytes.slice(2187, 162))
210
+ kerl.absorb(trits, 0, trits.length)
211
+
212
+ # Check if input transaction
213
+ if bundleTx.value < 0
214
+ address = bundleTx.address
215
+
216
+ newSignatureToValidate = {
217
+ address: address,
218
+ signatureFragments: [bundleTx.signatureMessageFragment]
219
+ }
220
+
221
+ # Find the subsequent txs with the remaining signature fragment
222
+ (index...bundle.length-1).step(1) do |i|
223
+ newBundleTx = bundle[i+1]
224
+
225
+ if newBundleTx.address == address && newBundleTx.value == 0
226
+ newSignatureToValidate[:signatureFragments] << newBundleTx.signatureMessageFragment
227
+ end
228
+ end
229
+
230
+ signaturesToValidate << newSignatureToValidate
231
+ end
232
+ end
233
+
234
+ # Check for total sum, if not equal 0 return error
235
+ return false if totalSum != 0
236
+
237
+ # get the bundle hash from the bundle transactions
238
+ kerl.squeeze(bundleFromTxs, 0, IOTA::Crypto::Kerl::HASH_LENGTH)
239
+ bundleFromTxs = IOTA::Crypto::Converter.trytes(bundleFromTxs)
240
+
241
+ # Check if bundle hash is the same as returned by tx object
242
+ return false if bundleFromTxs != bundleHash
243
+
244
+ # Last tx in the bundle should have currentIndex === lastIndex
245
+ return false if bundle[bundle.length - 1].currentIndex != bundle[bundle.length - 1].lastIndex
246
+
247
+ # Validate the signatures
248
+ (0...signaturesToValidate.length).step(1) do |i|
249
+ return false if !IOTA::Crypto::Signing.validateSignatures(signaturesToValidate[i][:address], signaturesToValidate[i][:signatureFragments], bundleHash)
250
+ end
251
+
252
+ true
253
+ end
254
+
255
+ def isValidChecksum(addressWithChecksum)
256
+ withoutChecksum = noChecksum(addressWithChecksum)
257
+ newWithCheckcum = addChecksum(withoutChecksum)
258
+ newWithCheckcum == addressWithChecksum
259
+ end
260
+
261
+ def validateSignatures(signedBundle, inputAddress)
262
+ bundleHash = nil
263
+ signatureFragments = []
264
+
265
+ signedBundle = signedBundle.transactions if signedBundle.class == IOTA::Models::Bundle
266
+
267
+ (0...signedBundle.length).step(1) do |i|
268
+ if signedBundle[i].address === inputAddress
269
+ bundleHash = signedBundle[i].bundle
270
+
271
+ signature = signedBundle[i].signatureMessageFragment
272
+
273
+ # if we reached remainder bundle
274
+ break if @validator.isString(signature) && @validator.isAllNine(signature)
275
+
276
+ signatureFragments << signature
277
+ end
278
+ end
279
+
280
+ return false if bundleHash.nil?
281
+
282
+ IOTA::Crypto::Signing.validateSignatures(inputAddress, signatureFragments, bundleHash)
283
+ end
284
+
285
+ def categorizeTransfers(transfers, addresses)
286
+ categorized = {
287
+ sent: [],
288
+ received: []
289
+ }
290
+
291
+ addresses = addresses.map { |a| a[0...81] }
292
+
293
+ # Iterate over all bundles and sort them between incoming and outgoing transfers
294
+ transfers.each do |bundle|
295
+ spentAlreadyAdded = false
296
+
297
+ bundle = IOTA::Models::Bundle.new(bundle) if bundle.class != IOTA::Models::Bundle
298
+
299
+ # Iterate over every bundle entry
300
+ bundle.transactions.each_with_index do |bundleEntry, bundleIndex|
301
+ address = bundleEntry.address[0...81]
302
+ if !addresses.index(address).nil?
303
+ # Check if it's a remainder address
304
+ isRemainder = (bundleEntry.currentIndex == bundleEntry.lastIndex) && bundleEntry.lastIndex != 0
305
+
306
+ # check if sent transaction
307
+ if bundleEntry.value < 0 && !spentAlreadyAdded && !isRemainder
308
+ categorized[:sent] << bundle
309
+
310
+ # too make sure we do not add transactions twice
311
+ spentAlreadyAdded = true
312
+ elsif bundleEntry.value >= 0 && !spentAlreadyAdded && !isRemainder
313
+ # check if received transaction, or 0 value (message)
314
+ # also make sure that this is not a 2nd tx for spent inputs
315
+ categorized[:received] << bundle
316
+ end
317
+ end
318
+ end
319
+ end
320
+ categorized
321
+ end
322
+ end
323
+ end
324
+ end