iota-ruby 1.0.1

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.
@@ -0,0 +1,44 @@
1
+ module IOTA
2
+ module Models
3
+ class Transfer < Base
4
+ attr_accessor :address, :message, :obsoleteTag, :value, :hmacKey
5
+
6
+ def initialize(options)
7
+ @utils = IOTA::Utils::Utils.new
8
+
9
+ options = symbolize_keys(options)
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)
20
+
21
+ @message = options[:message] || ''
22
+ @obsoleteTag = options[:tag] || options[:obsoleteTag] || ''
23
+ @value = options[:value]
24
+ @hmacKey = options[:hmacKey] || nil
25
+
26
+ if @hmacKey
27
+ @message = ('9'*244) + @message
28
+ end
29
+ end
30
+
31
+ def valid?
32
+ keysToValidate = [
33
+ { key: 'address', validator: :isAddress, args: nil },
34
+ { key: 'value', validator: :isValue, args: nil },
35
+ { key: 'message', validator: :isTrytes, args: nil },
36
+ { key: 'obsoleteTag', validator: :isTrytes, args: '0,27' }
37
+ ]
38
+
39
+ validator = IOTA::Utils::ObjectValidator.new(keysToValidate)
40
+ validator.valid?(self)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,41 @@
1
+ module IOTA
2
+ module Multisig
3
+ class Address
4
+ def initialize(digests = nil)
5
+ # Initialize kerl instance
6
+ @kerl = IOTA::Crypto::Kerl.new
7
+
8
+ # Add digests if passed
9
+ absorb(digests) if digests
10
+ end
11
+
12
+ def absorb(digest)
13
+ # Construct array
14
+ digests = digest.class == Array ? digest : [digest]
15
+
16
+ # Add digests
17
+ digests.each do |d|
18
+ # Get trits of digest
19
+ digestTrits = IOTA::Crypto::Converter.trits(d)
20
+
21
+ # Absorb
22
+ @kerl.absorb(digestTrits, 0, digestTrits.length)
23
+ end
24
+
25
+ self
26
+ end
27
+
28
+ def finalize(digest = nil)
29
+ # Absorb last digest if passed
30
+ absorb(digest) if digest
31
+
32
+ # Squeeze the address trits
33
+ addressTrits = []
34
+ @kerl.squeeze(addressTrits, 0, IOTA::Crypto::Kerl::HASH_LENGTH)
35
+
36
+ # Convert trits into trytes and return the address
37
+ IOTA::Crypto::Converter.trytes(addressTrits)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,244 @@
1
+ module IOTA
2
+ module Multisig
3
+ class Multisig
4
+ def initialize(client)
5
+ @api = client.api
6
+ @utils = client.utils
7
+ @validator = client.validator
8
+ @converter = IOTA::Crypto::Converter
9
+ @signing = IOTA::Crypto::Signing
10
+ end
11
+
12
+ def getKey(seed, index, security)
13
+ pk = getPrivateKey(seed, index, security)
14
+ @converter.trytes(pk.key)
15
+ end
16
+
17
+ def getDigest(seed, index, security)
18
+ pk = getPrivateKey(seed, index, security)
19
+ @converter.trytes(pk.digests)
20
+ end
21
+
22
+ def validateAddress(multisigAddress, digests)
23
+ kerl = IOTA::Crypto::Kerl.new
24
+
25
+ # Absorb all key digests
26
+ digests.each do |digest|
27
+ trits = @converter.trits(digest)
28
+ kerl.absorb(@converter.trits(digest), 0, trits.length)
29
+ end
30
+
31
+ # Squeeze address trits
32
+ addressTrits = []
33
+ kerl.squeeze(addressTrits, 0, IOTA::Crypto::Kerl::HASH_LENGTH)
34
+
35
+ # Convert trits into trytes and return the address
36
+ @converter.trytes(addressTrits) === multisigAddress
37
+ end
38
+
39
+ def initiateTransfer(input, remainderAddress = nil, transfers)
40
+ input = IOTA::Models::Input.new(input) if input.class != IOTA::Models::Input
41
+
42
+ # If message or tag is not supplied, provide it
43
+ # Also remove the checksum of the address if it's there
44
+ (0...transfers.length).step(1) do |i|
45
+ transfers[i] = IOTA::Models::Transfer.new(transfers[i]) if transfers[i].class != IOTA::Models::Transfer
46
+ end
47
+
48
+ # Input validation of transfers object
49
+ raise StandardError, "Invalid transfers provided" if !@validator.isTransfersArray(transfers)
50
+
51
+ # check if int
52
+ raise StandardError, "Invalid inputs provided" if !@validator.isValue(input.security)
53
+
54
+ # validate input address
55
+ raise StandardError, "Invalid input address provided" if !@validator.isAddress(input.address)
56
+
57
+ # validate remainder address
58
+ raise StandardError, "Invalid remainder address provided" if remainderAddress && !@validator.isAddress(remainderAddress)
59
+
60
+ remainderAddress = @utils.noChecksum(remainderAddress) if remainderAddress.length == 90
61
+
62
+ # Create a new bundle
63
+ bundle = IOTA::Crypto::Bundle.new
64
+
65
+ totalValue = 0
66
+ signatureFragments = []
67
+ tag = nil
68
+
69
+ # Iterate over all transfers, get totalValue and prepare the signatureFragments, message and tag
70
+ (0...transfers.length).step(1) do |i|
71
+ signatureMessageLength = 1
72
+
73
+ # If message longer than 2187 trytes, increase signatureMessageLength (add multiple transactions)
74
+ if transfers[i].message.length > 2187
75
+ # Get total length, message / maxLength (2187 trytes)
76
+ signatureMessageLength += (transfers[i].message.length / 2187).floor
77
+
78
+ msgCopy = transfers[i].message
79
+
80
+ # While there is still a message, copy it
81
+ while msgCopy
82
+ fragment = msgCopy.slice(0, 2187)
83
+ msgCopy = msgCopy.slice(2187, msgCopy.length)
84
+
85
+ # Pad remainder of fragment
86
+ fragment += (['9']*(2187-fragment.length)).join('') if fragment.length < 2187
87
+
88
+ signatureFragments.push(fragment)
89
+ end
90
+ else
91
+ # Else, get single fragment with 2187 of 9's trytes
92
+ fragment = ''
93
+
94
+ fragment = transfers[i].message.slice(0, 2187) if transfers[i].message
95
+
96
+ # Pad remainder of fragment
97
+ fragment += (['9']*(2187-fragment.length)).join('') if fragment.length < 2187
98
+
99
+ signatureFragments.push(fragment)
100
+ end
101
+
102
+ # get current timestamp in seconds
103
+ timestamp = Time.now.utc.to_i
104
+
105
+ # If no tag defined, get 27 tryte tag.
106
+ tag = transfers[i].obsoleteTag ? transfers[i].obsoleteTag : (['9']*27).join('')
107
+
108
+ # Pad for required 27 tryte length
109
+ tag += (['9']*(27-tag.length)).join('') if tag.length < 27
110
+
111
+ # Add first entries to the bundle
112
+ # Slice the address in case the user provided a checksummed one
113
+ bundle.addEntry(signatureMessageLength, transfers[i].address.slice(0, 81), transfers[i].value, tag, timestamp)
114
+
115
+ # Sum up total value
116
+ totalValue += transfers[i].value.to_i
117
+ end
118
+
119
+ # Get inputs if we are sending tokens
120
+ if totalValue > 0
121
+ if input.balance
122
+ return createBundle(input.balance, totalValue, bundle, input, remainderAddress, tag, signatureFragments)
123
+ else
124
+ @api.getBalances([input.address], 100) do |st1, balances|
125
+ if !st1
126
+ raise StandardError, "Error fetching balances: #{balances}"
127
+ else
128
+ return createBundle(balances['balances'][0].to_i, totalValue, bundle, input, remainderAddress, tag, signatureFragments)
129
+ end
130
+ end
131
+ end
132
+ else
133
+ raise StandardError, "Invalid value transfer: the transfer does not require a signature."
134
+ end
135
+ end
136
+
137
+ def addSignature(bundleToSign, inputAddress, key)
138
+ bundleToSign = [bundleToSign] if bundleToSign.class != Array
139
+ bundle = IOTA::Crypto::Bundle.new(bundleToSign)
140
+
141
+ # Get the security used for the private key
142
+ # 1 security level = 2187 trytes
143
+ security = (key.length / 2187).to_i
144
+
145
+ # convert private key trytes into trits
146
+ key = @converter.trits(key)
147
+
148
+ # First get the total number of already signed transactions
149
+ # use that for the bundle hash calculation as well as knowing
150
+ # where to add the signature
151
+ numSignedTxs = 0
152
+
153
+ (0...bundle.bundle.length).step(1) do |i|
154
+ bundle.bundle[i] = IOTA::Models::Transaction.new(bundle.bundle[i]) if bundle.bundle[i].class != IOTA::Models::Transaction
155
+ if bundle.bundle[i].address === inputAddress
156
+ # If transaction is already signed, increase counter
157
+ if !@validator.isAllNine(bundle.bundle[i].signatureMessageFragment)
158
+ numSignedTxs += 1
159
+ else
160
+ # sign the transactions
161
+ bundleHash = bundle.bundle[i].bundle
162
+
163
+ # First 6561 trits for the firstFragment
164
+ firstFragment = key.slice(0, 6561)
165
+
166
+ # Get the normalized bundle hash
167
+ normalizedBundleHash = bundle.normalizedBundle(bundleHash)
168
+ normalizedBundleFragments = []
169
+
170
+ # Split hash into 3 fragments
171
+ (0...3).step(1) do |k|
172
+ normalizedBundleFragments[k] = normalizedBundleHash.slice(k * 27, 27)
173
+ end
174
+
175
+ # First bundle fragment uses 27 trytes
176
+ firstBundleFragment = normalizedBundleFragments[numSignedTxs % 3]
177
+
178
+ # Calculate the new signatureFragment with the first bundle fragment
179
+ firstSignedFragment = @signing.signatureFragment(firstBundleFragment, firstFragment)
180
+
181
+ # Convert signature to trytes and assign the new signatureFragment
182
+ bundle.bundle[i].signatureMessageFragment = @converter.trytes(firstSignedFragment)
183
+
184
+ (1...security).step(1) do |j|
185
+ # Next 6561 trits for the firstFragment
186
+ nextFragment = key.slice(6561 * j, 6561)
187
+
188
+
189
+ # Use the next 27 trytes
190
+ nextBundleFragment = normalizedBundleFragments[(numSignedTxs + j) % 3]
191
+
192
+ # Calculate the new signatureFragment with the first bundle fragment
193
+ nextSignedFragment = @signing.signatureFragment(nextBundleFragment, nextFragment)
194
+
195
+ # Convert signature to trytes and add new bundle entry at i + j position
196
+ # Assign the signature fragment
197
+ bundle.bundle[i + j].signatureMessageFragment = @converter.trytes(nextSignedFragment)
198
+ end
199
+
200
+ break
201
+ end
202
+ end
203
+ end
204
+
205
+ bundle.bundle
206
+ end
207
+
208
+ private
209
+ def getPrivateKey(seed, index, security)
210
+ seed = IOTA::Models::Seed.new(seed) if seed.class != IOTA::Models::Seed
211
+ IOTA::Crypto::PrivateKey.new(seed.as_trits, index, security)
212
+ end
213
+
214
+ def createBundle(totalBalance, totalValue, bundle, input, remainderAddress, tag, signatureFragments)
215
+ if totalBalance > 0
216
+ toSubtract = 0 - totalBalance
217
+ timestamp = Time.now.utc.to_i
218
+
219
+ # Add input as bundle entry
220
+ # Only a single entry, signatures will be added later
221
+ bundle.addEntry(input.security, input.address, toSubtract, tag, timestamp)
222
+ end
223
+
224
+ raise StandardError, "Not enough balance" if totalValue > totalBalance
225
+
226
+ # If there is a remainder value
227
+ # Add extra output to send remaining funds to
228
+ if totalBalance > totalValue
229
+ remainder = totalBalance - totalValue
230
+
231
+ # Remainder bundle entry if necessary
232
+ return StandardError, "No remainder address defined" if !remainderAddress
233
+
234
+ bundle.addEntry(1, remainderAddress, remainder, tag, timestamp)
235
+ end
236
+
237
+ bundle.finalize()
238
+ bundle.addTrytes(signatureFragments)
239
+
240
+ bundle.bundle
241
+ end
242
+ end
243
+ end
244
+ end
@@ -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,117 @@
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)
9
+ @provider, @token = provider, token
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
+ request["Authorization"] = "token #{@token}" if @token
87
+
88
+ [uri, request, req_options]
89
+ end
90
+
91
+ def prepareResponse(result, command)
92
+ resultMap = {
93
+ 'getNeighbors' => 'neighbors',
94
+ 'addNeighbors' => 'addedNeighbors',
95
+ 'removeNeighbors' => 'removedNeighbors',
96
+ 'getTips' => 'hashes',
97
+ 'findTransactions' => 'hashes',
98
+ 'getTrytes' => 'trytes',
99
+ 'getInclusionStates' => 'states',
100
+ 'attachToTangle' => 'trytes'
101
+ }
102
+
103
+ # If correct result and we want to prepare the result
104
+ if result && resultMap.key?(command)
105
+ # If the response is from the sandbox, don't prepare the result
106
+ if command === 'attachToTangle' && result.key?('id')
107
+ result = result
108
+ else
109
+ result = result[resultMap[command]]
110
+ end
111
+ end
112
+
113
+ result
114
+ end
115
+ end
116
+ end
117
+ 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