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