Class: IOTA::Utils::Utils

Inherits:
Object
  • Object
show all
Includes:
Ascii
Defined in:
lib/iota/utils/utils.rb

Constant Summary collapse

UNIT_MAP =
{
  'i'  => 1,
  'Ki' => 1_000,
  'Mi' => 1_000_000,
  'Gi' => 1_000_000_000,
  'Ti' => 1_000_000_000_000,
  'Pi' => 1_000_000_000_000_000
}
UNIT_MAP_ORDER =
['Pi', 'Ti', 'Gi', 'Mi', 'Ki', 'i']

Constants included from Ascii

Ascii::TRYTE_VALUES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from Ascii

#fromTrytes, #toTrytes

Constructor Details

#initializeUtils

Returns a new instance of Utils.


21
22
23
# File 'lib/iota/utils/utils.rb', line 21

def initialize
  @validator = InputValidator.new
end

Instance Attribute Details

#validatorObject (readonly)

Returns the value of attribute validator


8
9
10
# File 'lib/iota/utils/utils.rb', line 8

def validator
  @validator
end

Instance Method Details

#addChecksum(input, checksumLength = 9, isAddress = true) ⇒ Object


145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/iota/utils/utils.rb', line 145

def addChecksum(input, checksumLength = 9, isAddress = true)
  # the length of the trytes to be validated
  validationLength = isAddress ? 81 : nil

  isSingleInput = @validator.isString(input)

  # If only single address, turn it into an array
  input = [input] if isSingleInput

  inputsWithChecksum = input.map do |inputValue|
    # check if correct trytes
    if !@validator.isTrytes(inputValue, validationLength)
      raise ArgumentError, "Invalid input provided"
    end

    kerl = IOTA::Crypto::Kerl.new

    # Address trits
    addressTrits = IOTA::Crypto::Converter.trits(inputValue)

    # Checksum trits
    checksumTrits = []

    # Absorb address trits
    kerl.absorb(addressTrits, 0, addressTrits.length)

    # Squeeze checksum trits
    kerl.squeeze(checksumTrits, 0, IOTA::Crypto::Curl::HASH_LENGTH)

    # First 9 trytes as checksum
    checksum = IOTA::Crypto::Converter.trytes(checksumTrits)[81-checksumLength...81]
    inputValue + checksum
  end

  isSingleInput ? inputsWithChecksum[0] : inputsWithChecksum
end

#categorizeTransfers(transfers, addresses) ⇒ Object


285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/iota/utils/utils.rb', line 285

def categorizeTransfers(transfers, addresses)
  categorized = {
    sent: [],
    received: []
  }

  addresses = addresses.map { |a| a[0...81] }

  # Iterate over all bundles and sort them between incoming and outgoing transfers
  transfers.each do |bundle|
    spentAlreadyAdded = false

    bundle = IOTA::Models::Bundle.new(bundle) if bundle.class != IOTA::Models::Bundle

    # Iterate over every bundle entry
    bundle.transactions.each_with_index do |bundleEntry, bundleIndex|
      address = bundleEntry.address[0...81]
      if !addresses.index(address).nil?        # Check if it's a remainder address

        isRemainder = (bundleEntry.currentIndex == bundleEntry.lastIndex) && bundleEntry.lastIndex != 0

        # check if sent transaction
        if bundleEntry.value < 0 && !spentAlreadyAdded && !isRemainder
          categorized[:sent] << bundle

          # too make sure we do not add transactions twice
          spentAlreadyAdded = true
        elsif bundleEntry.value >= 0 && !spentAlreadyAdded && !isRemainder          # check if received transaction, or 0 value (message)
          # also make sure that this is not a 2nd tx for spent inputs

          categorized[:received] << bundle
        end
      end
    end
  end
  categorized
end

#convertUnits(value, fromUnit, toUnit) ⇒ Object


25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/iota/utils/utils.rb', line 25

def convertUnits(value, fromUnit, toUnit)
  # Check if wrong unit provided
  if !UNIT_MAP[fromUnit] || !UNIT_MAP[toUnit]
    raise ArgumentError, "Invalid unit provided"
  end

  # If not valid value, throw error
  if !@validator.isNum(value)
    raise ArgumentError, "Invalid value"
  end

  converted = (BigDecimal(value.to_s) * UNIT_MAP[fromUnit]) / UNIT_MAP[toUnit]
  converted.to_f
end

#isBundle(bundle) ⇒ Object


182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/iota/utils/utils.rb', line 182

def isBundle(bundle)
  # If not correct bundle
  return false if !@validator.isArrayOfTxObjects(bundle)

  bundle = bundle.transactions if bundle.class == IOTA::Models::Bundle

  totalSum = 0
  bundleHash = bundle[0].bundle

  # Prepare to absorb txs and get bundleHash
  bundleFromTxs = []

  kerl = IOTA::Crypto::Kerl.new

  # Prepare for signature validation
  signaturesToValidate = []

  bundle.each_with_index do |bundleTx, index|
    totalSum += bundleTx.value

    # currentIndex has to be equal to the index in the array
    return false if bundleTx.currentIndex != index

    # Get the transaction trytes
    trytes = transactionTrytes(bundleTx)

    # Absorb bundle hash + value + timestamp + lastIndex + currentIndex trytes.
    trits = IOTA::Crypto::Converter.trits(trytes.slice(2187, 162))
    kerl.absorb(trits, 0, trits.length)

    # Check if input transaction
    if bundleTx.value < 0
      address = bundleTx.address

      newSignatureToValidate = {
        address: address,
        signatureFragments: [bundleTx.signatureMessageFragment]
      }

      # Find the subsequent txs with the remaining signature fragment
      (index...bundle.length-1).step(1) do |i|
        newBundleTx = bundle[i+1]

        if newBundleTx.address == address && newBundleTx.value == 0
          newSignatureToValidate[:signatureFragments] << newBundleTx.signatureMessageFragment
        end
      end

      signaturesToValidate << newSignatureToValidate
    end
  end

  # Check for total sum, if not equal 0 return error
  return false if totalSum != 0

  # get the bundle hash from the bundle transactions
  kerl.squeeze(bundleFromTxs, 0, IOTA::Crypto::Kerl::HASH_LENGTH)
  bundleFromTxs = IOTA::Crypto::Converter.trytes(bundleFromTxs)

  # Check if bundle hash is the same as returned by tx object
  return false if bundleFromTxs != bundleHash

  # Last tx in the bundle should have currentIndex === lastIndex
  return false if bundle[bundle.length - 1].currentIndex != bundle[bundle.length - 1].lastIndex

  # Validate the signatures
  (0...signaturesToValidate.length).step(1) do |i|
    return false if !IOTA::Crypto::Signing.validateSignatures(signaturesToValidate[i][:address], signaturesToValidate[i][:signatureFragments], bundleHash)
  end

  true
end

#isValidChecksum(addressWithChecksum) ⇒ Object


255
256
257
258
259
# File 'lib/iota/utils/utils.rb', line 255

def isValidChecksum(addressWithChecksum)
  withoutChecksum = noChecksum(addressWithChecksum)
  newWithCheckcum = addChecksum(withoutChecksum)
  newWithCheckcum == addressWithChecksum
end

#noChecksum(address) ⇒ Object


40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
# File 'lib/iota/utils/utils.rb', line 40

def noChecksum(address)
  isSingleAddress = @validator.isString(address)

  return address if isSingleAddress && address.length == 81

  # If only single address, turn it into an array
  if isSingleAddress
    address = [address]
  end

  addressesWithChecksum = []

  address.each do |addr|
    addressesWithChecksum << addr.slice(0, 81)
  end

  # return either string or the list
  if isSingleAddress
      return addressesWithChecksum.first
  else
      return addressesWithChecksum
  end
end

#transactionObject(trytes) ⇒ Object


64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/iota/utils/utils.rb', line 64

def transactionObject(trytes)
  return nil if !trytes

  # validity check
  (2279...2295).step(1) do |i|
    return nil if trytes[i] != "9"
  end

  trx = {}
  transactionTrits = IOTA::Crypto::Converter.trits(trytes)
  hash = []

  # generate the correct transaction hash
  curl = IOTA::Crypto::Curl.new
  curl.absorb(transactionTrits)
  curl.squeeze(hash)

  trx['hash'] = IOTA::Crypto::Converter.trytes(hash)
  trx['signatureMessageFragment'] = trytes.slice(0, 2187)
  trx['address'] = trytes.slice(2187, 81)
  trx['value'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6804, 33))
  trx['obsoleteTag'] = trytes.slice(2295, 27)
  trx['timestamp'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6966, 27))
  trx['currentIndex'] = IOTA::Crypto::Converter.value(transactionTrits.slice(6993, 27))
  trx['lastIndex'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7020, 27))
  trx['bundle'] = trytes.slice(2349, 81)
  trx['trunkTransaction'] = trytes.slice(2430, 81)
  trx['branchTransaction'] = trytes.slice(2511, 81)

  trx['tag'] = trytes.slice(2592, 27)
  trx['attachmentTimestamp'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7857, 27))
  trx['attachmentTimestampLowerBound'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7884, 27))
  trx['attachmentTimestampUpperBound'] = IOTA::Crypto::Converter.value(transactionTrits.slice(7911, 27))
  trx['nonce'] = trytes.slice(2646, 27)

  IOTA::Models::Transaction.new(trx)
end

#transactionTrytes(transaction) ⇒ Object


102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/iota/utils/utils.rb', line 102

def transactionTrytes(transaction)
  valueTrits = IOTA::Crypto::Converter.trits(transaction.value)
  valueTrits = valueTrits.concat([0]*(81-valueTrits.length)) if valueTrits.length < 81

  timestampTrits = IOTA::Crypto::Converter.trits(transaction.timestamp)
  timestampTrits = timestampTrits.concat([0]*(27-timestampTrits.length)) if timestampTrits.length < 27

  currentIndexTrits = IOTA::Crypto::Converter.trits(transaction.currentIndex)
  currentIndexTrits = currentIndexTrits.concat([0]*(27-currentIndexTrits.length)) if currentIndexTrits.length < 27

  lastIndexTrits = IOTA::Crypto::Converter.trits(transaction.lastIndex)
  lastIndexTrits = lastIndexTrits.concat([0]*(27-lastIndexTrits.length)) if lastIndexTrits.length < 27

  attachmentTimestampTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestamp || 0);
  attachmentTimestampTrits = attachmentTimestampTrits.concat([0]*(27-attachmentTimestampTrits.length)) if attachmentTimestampTrits.length < 27

  attachmentTimestampLowerBoundTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestampLowerBound || 0);
  attachmentTimestampLowerBoundTrits = attachmentTimestampLowerBoundTrits.concat([0]*(27-attachmentTimestampLowerBoundTrits.length)) if attachmentTimestampLowerBoundTrits.length < 27

  attachmentTimestampUpperBoundTrits = IOTA::Crypto::Converter.trits(transaction.attachmentTimestampUpperBound || 0);
  attachmentTimestampUpperBoundTrits = attachmentTimestampUpperBoundTrits.concat([0]*(27-attachmentTimestampUpperBoundTrits.length)) if attachmentTimestampUpperBoundTrits.length < 27

  tag = transaction.tag || transaction.obsoleteTag

  return (
    transaction.signatureMessageFragment +
    transaction.address +
    IOTA::Crypto::Converter.trytes(valueTrits) +
    transaction.obsoleteTag +
    IOTA::Crypto::Converter.trytes(timestampTrits) +
    IOTA::Crypto::Converter.trytes(currentIndexTrits) +
    IOTA::Crypto::Converter.trytes(lastIndexTrits) +
    transaction.bundle +
    transaction.trunkTransaction +
    transaction.branchTransaction +
    tag +
    IOTA::Crypto::Converter.trytes(attachmentTimestampTrits) +
    IOTA::Crypto::Converter.trytes(attachmentTimestampLowerBoundTrits) +
    IOTA::Crypto::Converter.trytes(attachmentTimestampUpperBoundTrits) +
    transaction.nonce
  )
end

#validateSignatures(signedBundle, inputAddress) ⇒ Object


261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
# File 'lib/iota/utils/utils.rb', line 261

def validateSignatures(signedBundle, inputAddress)
  bundleHash = nil
  signatureFragments = []

  signedBundle = signedBundle.transactions if signedBundle.class == IOTA::Models::Bundle

  (0...signedBundle.length).step(1) do |i|
    if signedBundle[i].address === inputAddress
      bundleHash = signedBundle[i].bundle

      signature = signedBundle[i].signatureMessageFragment

      # if we reached remainder bundle
      break if @validator.isString(signature) && @validator.isAllNine(signature)

      signatureFragments << signature
    end
  end

  return false if bundleHash.nil?

  IOTA::Crypto::Signing.validateSignatures(inputAddress, signatureFragments, bundleHash)
end