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
|