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,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