iota-ruby 1.1.8-java
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 +15 -0
- data/.travis.yml +24 -0
- data/.yardopts +7 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +3 -0
- data/LICENSE +21 -0
- data/README.md +121 -0
- data/Rakefile +36 -0
- data/bin/iota-console +15 -0
- data/examples/multisig.rb +69 -0
- data/ext/ccurl/ccurl.c +134 -0
- data/ext/ccurl/extconf.rb +22 -0
- data/ext/jcurl/JCurl.java +126 -0
- data/ext/jcurl/JCurlService.java +36 -0
- data/ext/pow/ccurl-0.3.0.dll +0 -0
- data/ext/pow/libccurl-0.3.0.dylib +0 -0
- data/ext/pow/libccurl-0.3.0.so +0 -0
- data/iota-ruby.gemspec +37 -0
- data/lib/iota.rb +76 -0
- data/lib/iota/api/api.rb +251 -0
- data/lib/iota/api/commands.rb +113 -0
- data/lib/iota/api/transport.rb +43 -0
- data/lib/iota/api/wrappers.rb +429 -0
- data/lib/iota/crypto/bundle.rb +163 -0
- data/lib/iota/crypto/converter.rb +244 -0
- data/lib/iota/crypto/curl.rb +18 -0
- data/lib/iota/crypto/curl_c.rb +17 -0
- data/lib/iota/crypto/curl_java.rb +18 -0
- data/lib/iota/crypto/curl_ruby.rb +70 -0
- data/lib/iota/crypto/hmac.rb +27 -0
- data/lib/iota/crypto/kerl.rb +82 -0
- data/lib/iota/crypto/pow_provider.rb +27 -0
- data/lib/iota/crypto/private_key.rb +80 -0
- data/lib/iota/crypto/sha3_ruby.rb +122 -0
- data/lib/iota/crypto/signing.rb +97 -0
- data/lib/iota/models/account.rb +489 -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 +124 -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 +324 -0
- data/lib/iota/version.rb +3 -0
- data/lib/jcurl.jar +0 -0
- data/lib/patch.rb +17 -0
- data/test/ascii_test.rb +114 -0
- data/test/curl_c_test.rb +31 -0
- data/test/curl_java_test.rb +31 -0
- data/test/curl_ruby_test.rb +27 -0
- data/test/kerl_test.rb +52 -0
- data/test/pow_provider_test.rb +36 -0
- data/test/sha3_test.rb +71 -0
- data/test/test_helper.rb +4 -0
- data/test/utils_test.rb +179 -0
- metadata +183 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Crypto
|
3
|
+
class RubyCurl
|
4
|
+
NUMBER_OF_ROUNDS = 81
|
5
|
+
HASH_LENGTH = 243
|
6
|
+
STATE_LENGTH = 3 * HASH_LENGTH
|
7
|
+
TRUTH_TABLE = [1, 0, -1, 1, -1, 0, -1, 1, 0]
|
8
|
+
|
9
|
+
def initialize(rounds = nil)
|
10
|
+
@rounds = rounds || NUMBER_OF_ROUNDS
|
11
|
+
reset
|
12
|
+
end
|
13
|
+
|
14
|
+
def reset
|
15
|
+
@state = [0] * STATE_LENGTH
|
16
|
+
end
|
17
|
+
|
18
|
+
def absorb(trits)
|
19
|
+
length = trits.length
|
20
|
+
offset = 0
|
21
|
+
|
22
|
+
while offset < length
|
23
|
+
start = offset
|
24
|
+
stop = [start + HASH_LENGTH, length].min
|
25
|
+
|
26
|
+
@state[0...stop-start] = trits.slice(start, stop-start)
|
27
|
+
transform
|
28
|
+
|
29
|
+
offset += HASH_LENGTH
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def squeeze(trits)
|
34
|
+
trits[0...HASH_LENGTH] = @state.slice(0, HASH_LENGTH)
|
35
|
+
transform
|
36
|
+
end
|
37
|
+
|
38
|
+
def transform
|
39
|
+
previousState = @state.slice(0, @state.length)
|
40
|
+
newState = @state.slice(0, @state.length)
|
41
|
+
|
42
|
+
index = 0
|
43
|
+
round = 0
|
44
|
+
while round < @rounds
|
45
|
+
previousTrit = previousState[index].to_i
|
46
|
+
|
47
|
+
pos = 0
|
48
|
+
while true
|
49
|
+
index += (index < 365) ? 364 : -365
|
50
|
+
newTrit = previousState[index].to_i
|
51
|
+
newState[pos] = TRUTH_TABLE[previousTrit + (3 * newTrit) + 4]
|
52
|
+
previousTrit = newTrit
|
53
|
+
pos += 1
|
54
|
+
break if pos >= STATE_LENGTH
|
55
|
+
end
|
56
|
+
|
57
|
+
previousState = newState
|
58
|
+
newState = newState.slice(0, newState.length)
|
59
|
+
round += 1
|
60
|
+
end
|
61
|
+
|
62
|
+
@state = newState
|
63
|
+
end
|
64
|
+
|
65
|
+
def version
|
66
|
+
"Ruby"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
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,82 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Crypto
|
3
|
+
class Kerl
|
4
|
+
BIT_HASH_LENGTH = 384
|
5
|
+
HASH_LENGTH = Curl::HASH_LENGTH
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
reset
|
9
|
+
end
|
10
|
+
|
11
|
+
def reset
|
12
|
+
unless RUBY_PLATFORM =~ /java/
|
13
|
+
require 'digest/sha3'
|
14
|
+
@hasher = Digest::SHA3.new(BIT_HASH_LENGTH)
|
15
|
+
else
|
16
|
+
require "iota/crypto/sha3_ruby"
|
17
|
+
@hasher = Digest::RubySHA3.new(BIT_HASH_LENGTH)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def absorb(trits, offset = 0, length = nil)
|
22
|
+
pad = trits.length % HASH_LENGTH != 0 ? trits.length % HASH_LENGTH : HASH_LENGTH
|
23
|
+
trits.concat([0] * (HASH_LENGTH - pad))
|
24
|
+
|
25
|
+
length = trits.length if length.nil?
|
26
|
+
|
27
|
+
if length % HASH_LENGTH != 0 || length == 0
|
28
|
+
raise StandardError, "Illegal length provided"
|
29
|
+
end
|
30
|
+
|
31
|
+
while offset < length
|
32
|
+
limit = [offset + HASH_LENGTH, length].min
|
33
|
+
|
34
|
+
# If we're copying over a full chunk, zero last trit
|
35
|
+
trits[limit - 1] = 0 if limit - offset == HASH_LENGTH
|
36
|
+
|
37
|
+
signed_bytes = Converter.convertToBytes(trits[offset...limit])
|
38
|
+
|
39
|
+
# Convert signed bytes into their equivalent unsigned representation
|
40
|
+
# In order to use Python's built-in bytes type
|
41
|
+
unsigned_bytes = signed_bytes.map{ |b| Converter.convertSign(b) }.pack('c*').force_encoding('UTF-8')
|
42
|
+
|
43
|
+
@hasher.update(unsigned_bytes)
|
44
|
+
|
45
|
+
offset += HASH_LENGTH
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def squeeze(trits, offset = 0, length = nil)
|
50
|
+
pad = trits.length % HASH_LENGTH != 0 ? trits.length % HASH_LENGTH : HASH_LENGTH
|
51
|
+
trits.concat([0] * (HASH_LENGTH - pad))
|
52
|
+
|
53
|
+
length = trits.length > 0 ? trits.length : HASH_LENGTH if length.nil?
|
54
|
+
|
55
|
+
if length % HASH_LENGTH != 0 || length == 0
|
56
|
+
raise StandardError, "Illegal length provided"
|
57
|
+
end
|
58
|
+
|
59
|
+
while offset < length
|
60
|
+
unsigned_hash = @hasher.digest
|
61
|
+
|
62
|
+
signed_hash = unsigned_hash.bytes.map { |b| Converter.convertSign(b) }
|
63
|
+
|
64
|
+
trits_from_hash = Converter.convertToTrits(signed_hash)
|
65
|
+
trits_from_hash[HASH_LENGTH - 1] = 0
|
66
|
+
|
67
|
+
limit = [HASH_LENGTH, length - offset].min
|
68
|
+
|
69
|
+
trits[offset...offset+limit] = trits_from_hash[0...limit]
|
70
|
+
|
71
|
+
flipped_bytes = unsigned_hash.bytes.map{ |b| Converter.convertSign(~b)}.pack('c*').force_encoding('UTF-8')
|
72
|
+
|
73
|
+
# Reset internal state before feeding back in
|
74
|
+
reset
|
75
|
+
@hasher.update(flipped_bytes)
|
76
|
+
|
77
|
+
offset += HASH_LENGTH
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'ffi'
|
2
|
+
|
3
|
+
module IOTA
|
4
|
+
module Crypto
|
5
|
+
class PowProvider
|
6
|
+
extend FFI::Library
|
7
|
+
|
8
|
+
ccurl_version = '0.3.0'
|
9
|
+
lib_root = File.join(File.dirname(__FILE__), '../../../ext/pow')
|
10
|
+
if FFI::Platform.windows?
|
11
|
+
libccurl_path = "#{lib_root}/ccurl-#{ccurl_version}.dll"
|
12
|
+
elsif FFI::Platform.mac?
|
13
|
+
libccurl_path = "#{lib_root}/libccurl-#{ccurl_version}.dylib"
|
14
|
+
else
|
15
|
+
libccurl_path = "#{lib_root}/libccurl-#{ccurl_version}.so"
|
16
|
+
end
|
17
|
+
|
18
|
+
ffi_lib libccurl_path
|
19
|
+
|
20
|
+
attach_function :ccurl_pow, [ :string, :int ], :string
|
21
|
+
attach_function :ccurl_digest_transaction, [ :string ], :string
|
22
|
+
|
23
|
+
alias_method :pow, :ccurl_pow
|
24
|
+
alias_method :digest, :ccurl_digest_transaction
|
25
|
+
end
|
26
|
+
end
|
27
|
+
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,122 @@
|
|
1
|
+
require 'digest'
|
2
|
+
|
3
|
+
module Digest
|
4
|
+
class RubySHA3 < Digest::Class
|
5
|
+
PILN = [10, 7, 11, 17, 18, 3, 5, 16,
|
6
|
+
8, 21, 24, 4, 15, 23, 19, 13,
|
7
|
+
12, 2, 20, 14, 22, 9, 6, 1]
|
8
|
+
|
9
|
+
ROTC = [ 1, 3, 6, 10, 15, 21, 28, 36,
|
10
|
+
45, 55, 2, 14, 27, 41, 56, 8,
|
11
|
+
25, 43, 62, 18, 39, 61, 20, 44]
|
12
|
+
|
13
|
+
RNDC = [0x0000000000000001, 0x0000000000008082, 0x800000000000808a,
|
14
|
+
0x8000000080008000, 0x000000000000808b, 0x0000000080000001,
|
15
|
+
0x8000000080008081, 0x8000000000008009, 0x000000000000008a,
|
16
|
+
0x0000000000000088, 0x0000000080008009, 0x000000008000000a,
|
17
|
+
0x000000008000808b, 0x800000000000008b, 0x8000000000008089,
|
18
|
+
0x8000000000008003, 0x8000000000008002, 0x8000000000000080,
|
19
|
+
0x000000000000800a, 0x800000008000000a, 0x8000000080008081,
|
20
|
+
0x8000000000008080, 0x0000000080000001, 0x8000000080008008]
|
21
|
+
|
22
|
+
def initialize hash_size = 512
|
23
|
+
@size = hash_size / 8
|
24
|
+
@buffer = ''
|
25
|
+
end
|
26
|
+
|
27
|
+
def << s
|
28
|
+
@buffer << s.unpack('C*').pack('C*')
|
29
|
+
self
|
30
|
+
end
|
31
|
+
alias update <<
|
32
|
+
|
33
|
+
def reset
|
34
|
+
# @buffer.clear # CHANGE: DO NOT CLEAR BUFFER AS WE NEED
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def digest(data = nil)
|
39
|
+
if data
|
40
|
+
update(data)
|
41
|
+
value = finish
|
42
|
+
else
|
43
|
+
cloned = dup
|
44
|
+
value = cloned.finish
|
45
|
+
end
|
46
|
+
value
|
47
|
+
end
|
48
|
+
|
49
|
+
def hexdigest(data = nil)
|
50
|
+
value = digest(data)
|
51
|
+
value.unpack("H*").first
|
52
|
+
end
|
53
|
+
|
54
|
+
def inspect
|
55
|
+
"#<#{self.class}: #{hexdigest}>"
|
56
|
+
end
|
57
|
+
|
58
|
+
def finish
|
59
|
+
s = Array.new 25, 0
|
60
|
+
width = 200 - @size * 2
|
61
|
+
|
62
|
+
buffer_dup = @buffer.dup
|
63
|
+
buffer_dup << "\x01" << "\0" * (width - buffer_dup.size % width)
|
64
|
+
buffer_dup[-1] = (buffer_dup[-1].ord | 0x80).chr.unpack('C*').pack('C*')
|
65
|
+
|
66
|
+
0.step buffer_dup.size - 1, width do |j|
|
67
|
+
quads = buffer_dup[j, width].unpack 'Q*'
|
68
|
+
(width / 8).times do |i|
|
69
|
+
s[i] ^= quads[i]
|
70
|
+
end
|
71
|
+
|
72
|
+
keccak s
|
73
|
+
end
|
74
|
+
|
75
|
+
s.pack('Q*')[0, @size]
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
def keccak s
|
80
|
+
24.times.each_with_object [] do |round, a|
|
81
|
+
# Theta
|
82
|
+
5.times do |i|
|
83
|
+
a[i] = s[i] ^ s[i + 5] ^ s[i + 10] ^ s[i + 15] ^ s[i + 20]
|
84
|
+
end
|
85
|
+
|
86
|
+
5.times do |i|
|
87
|
+
t = a[(i + 4) % 5] ^ rotate(a[(i + 1) % 5], 1)
|
88
|
+
0.step 24, 5 do |j|
|
89
|
+
s[j + i] ^= t
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Rho Pi
|
94
|
+
t = s[1]
|
95
|
+
24.times do |i|
|
96
|
+
j = PILN[i]
|
97
|
+
a[0] = s[j]
|
98
|
+
s[j] = rotate t, ROTC[i]
|
99
|
+
t = a[0]
|
100
|
+
end
|
101
|
+
|
102
|
+
# Chi
|
103
|
+
0.step 24, 5 do |j|
|
104
|
+
5.times do |i|
|
105
|
+
a[i] = s[j + i]
|
106
|
+
end
|
107
|
+
|
108
|
+
5.times do |i|
|
109
|
+
s[j + i] ^= ~a[(i + 1) % 5] & a[(i + 2) % 5]
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Iota
|
114
|
+
s[0] ^= RNDC[round]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
def rotate x, y
|
119
|
+
(x << y | x >> 64 - y) & (1 << 64) - 1
|
120
|
+
end
|
121
|
+
end
|
122
|
+
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
|