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