iota-ruby 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +8 -0
- data/.gitignore +12 -0
- data/.travis.yml +5 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +88 -0
- data/Rakefile +10 -0
- data/bin/iota-console +15 -0
- data/examples/multisig.rb +57 -0
- data/iota-ruby.gemspec +25 -0
- data/lib/iota/api/api.rb +194 -0
- data/lib/iota/api/commands.rb +99 -0
- data/lib/iota/api/wrappers.rb +402 -0
- data/lib/iota/crypto/bundle.rb +163 -0
- data/lib/iota/crypto/converter.rb +241 -0
- data/lib/iota/crypto/curl.rb +74 -0
- data/lib/iota/crypto/hmac.rb +27 -0
- data/lib/iota/crypto/kerl.rb +78 -0
- data/lib/iota/crypto/private_key.rb +80 -0
- data/lib/iota/crypto/signing.rb +97 -0
- data/lib/iota/models/account.rb +470 -0
- data/lib/iota/models/base.rb +13 -0
- data/lib/iota/models/bundle.rb +87 -0
- data/lib/iota/models/input.rb +38 -0
- data/lib/iota/models/seed.rb +33 -0
- data/lib/iota/models/transaction.rb +52 -0
- data/lib/iota/models/transfer.rb +44 -0
- data/lib/iota/multisig/address.rb +41 -0
- data/lib/iota/multisig/multisig.rb +244 -0
- data/lib/iota/utils/ascii.rb +50 -0
- data/lib/iota/utils/broker.rb +117 -0
- data/lib/iota/utils/input_validator.rb +149 -0
- data/lib/iota/utils/object_validator.rb +34 -0
- data/lib/iota/utils/utils.rb +326 -0
- data/lib/iota/version.rb +3 -0
- data/lib/iota.rb +73 -0
- data/test/ascii_test.rb +114 -0
- data/test/kerl_test.rb +64 -0
- data/test/test_helper.rb +4 -0
- data/test/utils_test.rb +173 -0
- metadata +144 -0
@@ -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
|