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,241 @@
1
+ module IOTA
2
+ module Crypto
3
+ class Converter
4
+ RADIX = 3
5
+ RADIX_BYTES = 256
6
+ MAX_TRIT_VALUE = 1
7
+ MIN_TRIT_VALUE = -1
8
+ BYTE_HASH_LENGTH = 48
9
+ HASH_LENGTH = IOTA::Crypto::Curl::HASH_LENGTH
10
+
11
+ # All possible tryte values
12
+ TRYTES_ALPHABET = "9ABCDEFGHIJKLMNOPQRSTUVWXYZ"
13
+
14
+ TRYTE_TRITS = [
15
+ [ 0, 0, 0],
16
+ [ 1, 0, 0],
17
+ [-1, 1, 0],
18
+ [ 0, 1, 0],
19
+ [ 1, 1, 0],
20
+ [-1, -1, 1],
21
+ [ 0, -1, 1],
22
+ [ 1, -1, 1],
23
+ [-1, 0, 1],
24
+ [ 0, 0, 1],
25
+ [ 1, 0, 1],
26
+ [-1, 1, 1],
27
+ [ 0, 1, 1],
28
+ [ 1, 1, 1],
29
+ [-1, -1, -1],
30
+ [ 0, -1, -1],
31
+ [ 1, -1, -1],
32
+ [-1, 0, -1],
33
+ [ 0, 0, -1],
34
+ [ 1, 0, -1],
35
+ [-1, 1, -1],
36
+ [ 0, 1, -1],
37
+ [ 1, 1, -1],
38
+ [-1, -1, 0],
39
+ [ 0, -1, 0],
40
+ [ 1, -1, 0],
41
+ [-1, 0, 0]
42
+ ]
43
+
44
+ class << self
45
+ def trits(input, state = [])
46
+ trits = state
47
+
48
+ if input.is_a? Integer
49
+ absoluteValue = input < 0 ? -input : input
50
+
51
+ while absoluteValue > 0
52
+ remainder = absoluteValue % 3
53
+ absoluteValue = (absoluteValue / 3).floor
54
+
55
+ if remainder > 1
56
+ remainder = -1
57
+ absoluteValue += 1
58
+ end
59
+
60
+ trits[trits.length] = remainder
61
+ end
62
+
63
+ if input < 0
64
+ (0... trits.length).step(1) do |i|
65
+ trits[i] = -trits[i]
66
+ end
67
+ end
68
+ else
69
+ (0... input.length).step(1) do |i|
70
+ index = TRYTES_ALPHABET.index(input[i])
71
+ tmp = i * 3
72
+ trits[tmp...tmp+3] = TRYTE_TRITS[index]
73
+ end
74
+ end
75
+
76
+ trits
77
+ end
78
+
79
+ def trytes(trits)
80
+ trytes = ""
81
+
82
+ (0...trits.length).step(3) do |i|
83
+ chunk = trits[i...i+3]
84
+ trytes += TRYTES_ALPHABET[TRYTE_TRITS.index(chunk)]
85
+ end
86
+
87
+ trytes
88
+ end
89
+
90
+ def value(trits)
91
+ returnValue = 0
92
+
93
+ range = (trits.length..0)
94
+
95
+ range.first.downto(range.last).each do |i|
96
+ returnValue = returnValue * 3 + trits[i].to_i
97
+ end
98
+
99
+ returnValue
100
+ end
101
+
102
+ def fromValue(value)
103
+ destination = []
104
+ absoluteValue = value < 0 ? -value : value
105
+ i = 0
106
+
107
+ while absoluteValue > 0
108
+ remainder = absoluteValue % RADIX
109
+ absoluteValue = (absoluteValue / RADIX).floor
110
+
111
+ if remainder > MAX_TRIT_VALUE
112
+ remainder = MIN_TRIT_VALUE
113
+ absoluteValue += 1
114
+ end
115
+ destination[i] = remainder
116
+ i += 1
117
+ end
118
+
119
+ if value < 0
120
+ (0...destination.length).step(1) do |j|
121
+ # switch values
122
+ destination[j] = destination[j] == 0 ? 0 : -destination[j]
123
+ end
124
+ end
125
+
126
+ destination
127
+ end
128
+
129
+ ### ADOPTED FROM PYTHON LIBRARY
130
+ # Word to tryte & trytes to words conversion
131
+ def convertToBytes(trits)
132
+ bigInt = convertBaseToBigInt(trits, 3)
133
+ bytes_k = convertBigIntToBytes(bigInt)
134
+ bytes_k
135
+ end
136
+
137
+ def convertToTrits(bytes)
138
+ bigInt = convertBytesToBigInt(bytes)
139
+ trits = convertBigIntToBase(bigInt, 3, HASH_LENGTH)
140
+ trits
141
+ end
142
+
143
+ # Convert between signed and unsigned bytes
144
+ def convertSign(byte)
145
+ if byte < 0
146
+ return 256 + byte
147
+ elsif byte > 127
148
+ return -256 + byte
149
+ end
150
+ byte
151
+ end
152
+
153
+ def convertBaseToBigInt(array, base)
154
+ bigint = 0
155
+ (0...array.length).step(1) do |i|
156
+ bigint += array[i] * (base ** i)
157
+ end
158
+ bigint
159
+ end
160
+
161
+ def convertBigIntToBase(bigInt, base, length)
162
+ result = []
163
+
164
+ isNegative = bigInt < 0
165
+ quotient = bigInt.abs
166
+
167
+ max, _ = (isNegative ? base : base-1).divmod(2)
168
+
169
+ length.times do
170
+ quotient, remainder = quotient.divmod(base)
171
+
172
+ if remainder > max
173
+ # Lend 1 to the next place so we can make this digit negative.
174
+ quotient += 1
175
+ remainder -= base
176
+ end
177
+
178
+ remainder = -remainder if isNegative
179
+
180
+ result << remainder
181
+ end
182
+
183
+ result
184
+ end
185
+
186
+ def convertBigIntToBytes(big)
187
+ bytesArrayTemp = []
188
+
189
+ (0...48).step(1) do |pos|
190
+ bytesArrayTemp << (big.abs >> pos * 8) % (1 << 8)
191
+ end
192
+
193
+ # big endian and balanced
194
+ bytesArray = bytesArrayTemp.reverse.map { |x| x <= 0x7F ? x : x - 0x100 }
195
+
196
+ if big < 0
197
+ # 1-compliment
198
+ bytesArray = bytesArray.map { |x| ~x }
199
+
200
+ # add1
201
+ (0...bytesArray.length).reverse_each do |pos|
202
+ add = (bytesArray[pos] & 0xFF) + 1
203
+ bytesArray[pos] = add <= 0x7F ? add : add - 0x100
204
+ break if bytesArray[pos] != 0
205
+ end
206
+ end
207
+
208
+ bytesArray
209
+ end
210
+
211
+ def convertBytesToBigInt(array)
212
+ # copy of array
213
+ bytesArray = array.map { |x| x }
214
+
215
+ # number sign in MSB
216
+ signum = bytesArray[0] >= 0 ? 1 : -1
217
+
218
+ if signum == -1
219
+ # sub1
220
+ (0...bytesArray.length).reverse_each do |pos|
221
+ sub = (bytesArray[pos] & 0xFF) - 1
222
+ bytesArray[pos] = sub <= 0x7F ? sub : sub - 0x100
223
+ break if bytesArray[pos] != -1
224
+ end
225
+
226
+ # 1-compliment
227
+ bytesArray = bytesArray.map { |x| ~x }
228
+ end
229
+
230
+ # sum magnitudes and set sign
231
+ sum = 0
232
+ bytesArray.reverse.each_with_index do |v, pos|
233
+ sum += (v & 0xFF) << pos * 8
234
+ end
235
+
236
+ sum * signum
237
+ end
238
+ end
239
+ end
240
+ end
241
+ end
@@ -0,0 +1,74 @@
1
+ module IOTA
2
+ module Crypto
3
+ class Curl
4
+ NUMBER_OF_ROUNDS = 81
5
+ HASH_LENGTH = 243
6
+ STATE_LENGTH = 3 * HASH_LENGTH
7
+
8
+ def initialize(rounds = nil)
9
+ @rounds = rounds || NUMBER_OF_ROUNDS
10
+ @truthTable = [1, 0, -1, 2, 1, -1, 0, 2, -1, 1, 0]
11
+ end
12
+
13
+ def setup(state = nil)
14
+ if state
15
+ @state = state
16
+ else
17
+ @state = []
18
+ STATE_LENGTH.times {|a| @state << 0}
19
+ end
20
+ end
21
+
22
+ def reset
23
+ setup
24
+ end
25
+
26
+ def absorb(trits, offset, length)
27
+ loop do
28
+ i = 0
29
+ limit = length < HASH_LENGTH ? length : HASH_LENGTH
30
+
31
+ while i < limit
32
+ @state[i] = trits[offset]
33
+ i += 1
34
+ offset += 1
35
+ end
36
+
37
+ transform
38
+ length -= HASH_LENGTH
39
+
40
+ break if length <= 0
41
+ end
42
+ end
43
+
44
+ def squeeze(trits, offset, length)
45
+ loop do
46
+ i = 0
47
+ limit = length < HASH_LENGTH ? length : HASH_LENGTH
48
+ while i < limit
49
+ trits[offset] = @state[i]
50
+ i += 1
51
+ offset += 1
52
+ end
53
+
54
+ transform
55
+ length -= HASH_LENGTH
56
+
57
+ break if length <= 0
58
+ end
59
+ end
60
+
61
+ def transform
62
+ stateCopy = []
63
+ index = 0
64
+
65
+ (0...@rounds).step(1) do |_|
66
+ stateCopy = @state.slice(0, @state.length)
67
+ (0...STATE_LENGTH).step(1) do |i|
68
+ @state[i] = @truthTable[stateCopy[index].to_i + (stateCopy[index += (index < 365 ? 364 : -365)].to_i << 2) + 5]
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,27 @@
1
+ module IOTA
2
+ module Crypto
3
+ class Hmac
4
+ ROUNDS = 27
5
+
6
+ def initialize(key)
7
+ @key = Converter.trits(key)
8
+ end
9
+
10
+ def addHMAC(bundle)
11
+ curl = Curl.new(ROUNDS)
12
+ (0...bundle.bundle.length).step(1) do |i|
13
+ if bundle.bundle[i].value > 0
14
+ bundleHashTrits = Converter.trits(bundle.bundle[i].bundle)
15
+ hmac = Array.new(243, 0)
16
+ curl.reset
17
+ curl.absorb(@key)
18
+ curl.absorb(bundleHashTrits)
19
+ curl.squeeze(hmac)
20
+ hmacTrytes = Converter.trytes(hmac)
21
+ bundle.bundle[i].signatureMessageFragment = hmacTrytes + bundle.bundle[i].signatureMessageFragment[81...2187]
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,78 @@
1
+ require 'digest/sha3'
2
+
3
+ module IOTA
4
+ module Crypto
5
+ class Kerl
6
+ BIT_HASH_LENGTH = 384
7
+ HASH_LENGTH = Curl::HASH_LENGTH
8
+
9
+ def initialize
10
+ reset
11
+ end
12
+
13
+ def reset
14
+ @hasher = Digest::SHA3.new(BIT_HASH_LENGTH)
15
+ end
16
+
17
+ def absorb(trits, offset = 0, length = nil)
18
+ pad = trits.length % HASH_LENGTH != 0 ? trits.length % HASH_LENGTH : HASH_LENGTH
19
+ trits.concat([0] * (HASH_LENGTH - pad))
20
+
21
+ length = trits.length if length.nil?
22
+
23
+ if length % HASH_LENGTH != 0 || length == 0
24
+ raise StandardError, "Illegal length provided"
25
+ end
26
+
27
+ while offset < length
28
+ limit = [offset + HASH_LENGTH, length].min
29
+
30
+ # If we're copying over a full chunk, zero last trit
31
+ trits[limit - 1] = 0 if limit - offset == HASH_LENGTH
32
+
33
+ signed_bytes = Converter.convertToBytes(trits[offset...limit])
34
+
35
+ # Convert signed bytes into their equivalent unsigned representation
36
+ # In order to use Python's built-in bytes type
37
+ unsigned_bytes = signed_bytes.map{ |b| Converter.convertSign(b) }.pack('c*').force_encoding('UTF-8')
38
+
39
+ @hasher.update(unsigned_bytes)
40
+
41
+ offset += HASH_LENGTH
42
+ end
43
+ end
44
+
45
+ def squeeze(trits, offset = 0, length = nil)
46
+ pad = trits.length % HASH_LENGTH != 0 ? trits.length % HASH_LENGTH : HASH_LENGTH
47
+ trits.concat([0] * (HASH_LENGTH - pad))
48
+
49
+ length = trits.length > 0 ? trits.length : HASH_LENGTH if length.nil?
50
+
51
+ if length % HASH_LENGTH != 0 || length == 0
52
+ raise StandardError, "Illegal length provided"
53
+ end
54
+
55
+ while offset < length
56
+ unsigned_hash = @hasher.digest
57
+
58
+ signed_hash = unsigned_hash.bytes.map { |b| Converter.convertSign(b) }
59
+
60
+ trits_from_hash = Converter.convertToTrits(signed_hash)
61
+ trits_from_hash[HASH_LENGTH - 1] = 0
62
+
63
+ limit = [HASH_LENGTH, length - offset].min
64
+
65
+ trits[offset...offset+limit] = trits_from_hash[0...limit]
66
+
67
+ flipped_bytes = unsigned_hash.bytes.map{ |b| Converter.convertSign(~b)}.pack('c*').force_encoding('UTF-8')
68
+
69
+ # Reset internal state before feeding back in
70
+ reset
71
+ @hasher.update(flipped_bytes)
72
+
73
+ offset += HASH_LENGTH
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,80 @@
1
+ module IOTA
2
+ module Crypto
3
+ class PrivateKey
4
+ HASH_LENGTH = Kerl::HASH_LENGTH
5
+
6
+ attr_reader :key
7
+
8
+ def initialize(seed, index, security)
9
+ key = []
10
+ offset = 0
11
+ buffer = []
12
+
13
+ (0...index).step(1) do |i|
14
+ # Treat ``seed`` like a really big number and add ``index``.
15
+ # Note that addition works a little bit differently in balanced ternary.
16
+ (0...seed.length).step(1) do |j|
17
+ seed[j] += 1
18
+
19
+ if seed[j] > 1
20
+ seed[j] = -1
21
+ else
22
+ break
23
+ end
24
+ end
25
+ end
26
+
27
+ kerl = Kerl.new
28
+ kerl.absorb(seed, 0, seed.length)
29
+ kerl.squeeze(seed, 0, seed.length)
30
+ kerl.reset
31
+ kerl.absorb(seed, 0, seed.length)
32
+
33
+ security.times do
34
+ (0...27).step(1) do |i|
35
+ kerl.squeeze(buffer, 0, seed.length)
36
+ (0...HASH_LENGTH).step(1) do |j|
37
+ key[offset] = buffer[j]
38
+ offset += 1
39
+ end
40
+ end
41
+ end
42
+
43
+ @key = key
44
+ end
45
+
46
+ def digests
47
+ digestsArray = []
48
+ buffer = []
49
+
50
+ (0...(@key.length / 6561).floor).step(1) do |i|
51
+ keyFragment = @key.slice(i * 6561, 6561)
52
+
53
+ (0...27).step(1) do |j|
54
+ buffer = keyFragment.slice(j * HASH_LENGTH, HASH_LENGTH);
55
+
56
+ (0...26).step(1) do |k|
57
+ kKerl = Kerl.new
58
+ kKerl.absorb(buffer, 0, buffer.length)
59
+ kKerl.squeeze(buffer, 0, HASH_LENGTH)
60
+ end
61
+
62
+ (0...HASH_LENGTH).step(1) do |k|
63
+ keyFragment[j * HASH_LENGTH + k] = buffer[k]
64
+ end
65
+ end
66
+
67
+ kerl = Kerl.new
68
+ kerl.absorb(keyFragment, 0, keyFragment.length)
69
+ kerl.squeeze(buffer, 0, HASH_LENGTH)
70
+
71
+ (0...HASH_LENGTH).step(1) do |j|
72
+ digestsArray[i * HASH_LENGTH + j] = buffer[j];
73
+ end
74
+ end
75
+
76
+ digestsArray
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,97 @@
1
+ module IOTA
2
+ module Crypto
3
+ class Signing
4
+ HASH_LENGTH = Kerl::HASH_LENGTH
5
+
6
+ class << self
7
+ def address(digests)
8
+ addressTrits = []
9
+
10
+ kerl = Kerl.new
11
+
12
+ kerl.absorb(digests, 0, digests.length)
13
+ kerl.squeeze(addressTrits, 0, Kerl::HASH_LENGTH)
14
+
15
+ addressTrits
16
+ end
17
+
18
+ def digest(normalizedBundleFragment, signatureFragment)
19
+ buffer = []
20
+ kerl = Kerl.new
21
+
22
+ (0...27).step(1) do |i|
23
+ buffer = signatureFragment.slice(i * HASH_LENGTH, HASH_LENGTH)
24
+
25
+ j = normalizedBundleFragment[i] + 13
26
+
27
+ while j > 0
28
+ jKerl = Kerl.new
29
+ jKerl.absorb(buffer, 0, buffer.length)
30
+ jKerl.squeeze(buffer, 0, HASH_LENGTH)
31
+ j -= 1
32
+ end
33
+
34
+ kerl.absorb(buffer, 0, buffer.length)
35
+ end
36
+
37
+ kerl.squeeze(buffer, 0, HASH_LENGTH)
38
+ buffer
39
+ end
40
+
41
+ def signatureFragment(normalizedBundleFragment, keyFragment)
42
+ signatureFragment = keyFragment.slice(0, keyFragment.length)
43
+ hash = []
44
+
45
+ kerl = Kerl.new
46
+
47
+ (0...27).step(1) do |i|
48
+ hash = signatureFragment.slice(i * HASH_LENGTH, HASH_LENGTH)
49
+
50
+ (0...13-normalizedBundleFragment[i]).step(1) do |j|
51
+ kerl.reset
52
+ kerl.absorb(hash, 0, hash.length)
53
+ kerl.squeeze(hash, 0, HASH_LENGTH)
54
+ end
55
+
56
+ (0...HASH_LENGTH).step(1) do |j|
57
+ signatureFragment[i * HASH_LENGTH + j] = hash[j]
58
+ end
59
+ end
60
+
61
+ signatureFragment
62
+ end
63
+
64
+ def validateSignatures(expectedAddress, signatureFragments, bundleHash)
65
+ if !bundleHash
66
+ raise StandardError, "Invalid bundle hash provided"
67
+ end
68
+
69
+ bundle = Bundle.new
70
+
71
+ normalizedBundleFragments = []
72
+ normalizedBundleHash = bundle.normalizedBundle(bundleHash)
73
+
74
+ # Split hash into 3 fragments
75
+ (0...3).step(1) do |i|
76
+ normalizedBundleFragments[i] = normalizedBundleHash.slice(i * 27, 27)
77
+ end
78
+
79
+ # Get digests
80
+ digests = []
81
+ (0...signatureFragments.length).step(1) do |i|
82
+ digestBuffer = digest(normalizedBundleFragments[i % 3], Converter.trits(signatureFragments[i]))
83
+
84
+ (0...HASH_LENGTH).step(1) do |j|
85
+ digests[i * 243 + j] = digestBuffer[j]
86
+ end
87
+ end
88
+
89
+ addressTrits = address(digests)
90
+ address = Converter.trytes(addressTrits)
91
+
92
+ expectedAddress == address
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end