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,87 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Models
|
3
|
+
class Bundle < Base
|
4
|
+
attr_reader :transactions, :persistence, :attachmentTimestamp
|
5
|
+
|
6
|
+
def initialize(transactions)
|
7
|
+
if transactions.class != Array
|
8
|
+
raise StandardError, "Invalid transactions array"
|
9
|
+
end
|
10
|
+
|
11
|
+
@transactions = []
|
12
|
+
transactions.each do |trx|
|
13
|
+
trx = Transaction.new(trx) if trx.class != IOTA::Models::Transaction
|
14
|
+
@transactions << trx
|
15
|
+
end
|
16
|
+
|
17
|
+
@persistence = @transactions.first.persistence
|
18
|
+
@attachmentTimestamp = @transactions.first.attachmentTimestamp
|
19
|
+
end
|
20
|
+
|
21
|
+
def extractJSON
|
22
|
+
utils = IOTA::Utils::Utils.new
|
23
|
+
|
24
|
+
# Sanity check: if the first tryte pair is not opening bracket, it's not a message
|
25
|
+
firstTrytePair = transactions[0].signatureMessageFragment[0] + transactions[0].signatureMessageFragment[1]
|
26
|
+
|
27
|
+
return nil if firstTrytePair != "OD"
|
28
|
+
|
29
|
+
index = 0
|
30
|
+
notEnded = true
|
31
|
+
trytesChunk = ''
|
32
|
+
trytesChecked = 0
|
33
|
+
preliminaryStop = false
|
34
|
+
finalJson = ''
|
35
|
+
|
36
|
+
while index < transactions.length && notEnded
|
37
|
+
messageChunk = transactions[index].signatureMessageFragment
|
38
|
+
|
39
|
+
# We iterate over the message chunk, reading 9 trytes at a time
|
40
|
+
(0...messageChunk.length).step(9) do |i|
|
41
|
+
# get 9 trytes
|
42
|
+
trytes = messageChunk.slice(i, 9)
|
43
|
+
trytesChunk += trytes
|
44
|
+
|
45
|
+
# Get the upper limit of the tytes that need to be checked
|
46
|
+
# because we only check 2 trytes at a time, there is sometimes a leftover
|
47
|
+
upperLimit = trytesChunk.length - trytesChunk.length % 2
|
48
|
+
|
49
|
+
trytesToCheck = trytesChunk[trytesChecked...upperLimit]
|
50
|
+
|
51
|
+
# We read 2 trytes at a time and check if it equals the closing bracket character
|
52
|
+
(0...trytesToCheck.length).step(2) do |j|
|
53
|
+
trytePair = trytesToCheck[j] + trytesToCheck[j + 1]
|
54
|
+
|
55
|
+
# If closing bracket char was found, and there are only trailing 9's
|
56
|
+
# we quit and remove the 9's from the trytesChunk.
|
57
|
+
if preliminaryStop && trytePair == '99'
|
58
|
+
notEnded = false
|
59
|
+
break
|
60
|
+
end
|
61
|
+
|
62
|
+
finalJson += utils.fromTrytes(trytePair)
|
63
|
+
|
64
|
+
# If tryte pair equals closing bracket char, we set a preliminary stop
|
65
|
+
# the preliminaryStop is useful when we have a nested JSON object
|
66
|
+
if trytePair === "QD"
|
67
|
+
preliminaryStop = true
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
break if !notEnded
|
72
|
+
|
73
|
+
trytesChecked += trytesToCheck.length;
|
74
|
+
end
|
75
|
+
|
76
|
+
# If we have not reached the end of the message yet, we continue with the next transaction in the bundle
|
77
|
+
index += 1
|
78
|
+
end
|
79
|
+
|
80
|
+
# If we did not find any JSON, return nil
|
81
|
+
return nil if notEnded
|
82
|
+
|
83
|
+
finalJson
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Models
|
3
|
+
class Input < Base
|
4
|
+
attr_accessor :address, :keyIndex, :security, :balance
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
utils = IOTA::Utils::Utils.new
|
8
|
+
options = symbolize_keys(options)
|
9
|
+
|
10
|
+
@address = options[:address] || nil
|
11
|
+
if @address.nil?
|
12
|
+
raise StandardError, "address not provided for transfer"
|
13
|
+
end
|
14
|
+
|
15
|
+
if @address.length == 90 && !utils.isValidChecksum(@address)
|
16
|
+
raise StandardError, "Invalid checksum: #{thisTransfer[:address]}"
|
17
|
+
end
|
18
|
+
|
19
|
+
@address = utils.noChecksum(@address) if @address.length == 90
|
20
|
+
|
21
|
+
@keyIndex = options[:keyIndex]
|
22
|
+
@security = options[:security]
|
23
|
+
@balance = options[:balance]
|
24
|
+
end
|
25
|
+
|
26
|
+
def valid?
|
27
|
+
keysToValidate = [
|
28
|
+
{ key: 'address', validator: :isAddress, args: nil },
|
29
|
+
{ key: 'security', validator: :isValue, args: nil },
|
30
|
+
{ key: 'keyIndex', validator: :isValue, args: nil }
|
31
|
+
]
|
32
|
+
|
33
|
+
validator = IOTA::Utils::ObjectValidator.new(keysToValidate)
|
34
|
+
validator.valid?(self)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Models
|
3
|
+
class Seed < Base
|
4
|
+
def initialize(seed)
|
5
|
+
@utils = IOTA::Utils::Utils.new
|
6
|
+
|
7
|
+
# Check if correct seed
|
8
|
+
if seed.class == String && !@utils.validator.isTrytes(seed)
|
9
|
+
raise StandardError, "Invalid seed provided"
|
10
|
+
end
|
11
|
+
|
12
|
+
seed += "9" * (81 - seed.length) if seed.length < 81
|
13
|
+
|
14
|
+
@seed = seed
|
15
|
+
end
|
16
|
+
|
17
|
+
def getAddress(index, security, checksum)
|
18
|
+
pk = IOTA::Crypto::PrivateKey.new(self.as_trits, index, security)
|
19
|
+
address_trits = IOTA::Crypto::Signing.address(pk.digests)
|
20
|
+
address = IOTA::Crypto::Converter.trytes(address_trits)
|
21
|
+
|
22
|
+
address = @utils.addChecksum(address) if checksum
|
23
|
+
|
24
|
+
address
|
25
|
+
end
|
26
|
+
|
27
|
+
# Converter methods
|
28
|
+
def as_trits
|
29
|
+
IOTA::Crypto::Converter.trits(@seed)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Models
|
3
|
+
class Transaction < Base
|
4
|
+
attr_accessor :hash, :signatureMessageFragment, :address, :value, :obsoleteTag, :timestamp, :currentIndex, :lastIndex, :bundle, :trunkTransaction, :branchTransaction, :tag, :attachmentTimestamp, :attachmentTimestampLowerBound, :attachmentTimestampUpperBound, :nonce, :persistence
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
options = symbolize_keys(options)
|
8
|
+
@hash = options[:hash]
|
9
|
+
@signatureMessageFragment = options[:signatureMessageFragment]
|
10
|
+
@address = options[:address]
|
11
|
+
@value = options[:value]
|
12
|
+
@obsoleteTag = options[:obsoleteTag]
|
13
|
+
@timestamp = options[:timestamp]
|
14
|
+
@currentIndex = options[:currentIndex]
|
15
|
+
@lastIndex = options[:lastIndex]
|
16
|
+
@bundle = options[:bundle]
|
17
|
+
@trunkTransaction = options[:trunkTransaction]
|
18
|
+
@branchTransaction = options[:branchTransaction]
|
19
|
+
@tag = options[:tag]
|
20
|
+
@attachmentTimestamp = options[:attachmentTimestamp]
|
21
|
+
@attachmentTimestampLowerBound = options[:attachmentTimestampLowerBound]
|
22
|
+
@attachmentTimestampUpperBound = options[:attachmentTimestampUpperBound]
|
23
|
+
@nonce = options[:nonce]
|
24
|
+
@persistence = nil
|
25
|
+
end
|
26
|
+
|
27
|
+
def valid?
|
28
|
+
keysToValidate = [
|
29
|
+
{ key: 'hash', validator: :isHash, args: nil},
|
30
|
+
{ key: 'signatureMessageFragment', validator: :isTrytes, args: 2187 },
|
31
|
+
{ key: 'address', validator: :isHash, args: nil },
|
32
|
+
{ key: 'value', validator: :isValue, args: nil },
|
33
|
+
{ key: 'obsoleteTag', validator: :isTrytes, args: 27 },
|
34
|
+
{ key: 'timestamp', validator: :isValue, args: nil },
|
35
|
+
{ key: 'currentIndex', validator: :isValue, args: nil },
|
36
|
+
{ key: 'lastIndex', validator: :isValue, args: nil },
|
37
|
+
{ key: 'bundle', validator: :isHash, args: nil },
|
38
|
+
{ key: 'trunkTransaction', validator: :isHash, args: nil },
|
39
|
+
{ key: 'branchTransaction', validator: :isHash, args: nil },
|
40
|
+
{ key: 'tag', validator: :isTrytes, args: 27 },
|
41
|
+
{ key: 'attachmentTimestamp', validator: :isValue, args: nil },
|
42
|
+
{ key: 'attachmentTimestampLowerBound', validator: :isValue, args: nil },
|
43
|
+
{ key: 'attachmentTimestampUpperBound', validator: :isValue, args: nil },
|
44
|
+
{ key: 'nonce', validator: :isTrytes, args: 27 }
|
45
|
+
]
|
46
|
+
|
47
|
+
validator = IOTA::Utils::ObjectValidator.new(keysToValidate)
|
48
|
+
validator.valid?(self)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Models
|
3
|
+
class Transfer < Base
|
4
|
+
attr_accessor :address, :message, :obsoleteTag, :value, :hmacKey
|
5
|
+
|
6
|
+
def initialize(options)
|
7
|
+
@utils = IOTA::Utils::Utils.new
|
8
|
+
|
9
|
+
options = symbolize_keys(options)
|
10
|
+
@address = options[:address] || nil
|
11
|
+
if @address.nil?
|
12
|
+
raise StandardError, "address not provided for transfer"
|
13
|
+
end
|
14
|
+
|
15
|
+
if @address.length == 90 && !@utils.isValidChecksum(@address)
|
16
|
+
raise StandardError, "Invalid checksum: #{thisTransfer[:address]}"
|
17
|
+
end
|
18
|
+
|
19
|
+
@address = @utils.noChecksum(@address)
|
20
|
+
|
21
|
+
@message = options[:message] || ''
|
22
|
+
@obsoleteTag = options[:tag] || options[:obsoleteTag] || ''
|
23
|
+
@value = options[:value]
|
24
|
+
@hmacKey = options[:hmacKey] || nil
|
25
|
+
|
26
|
+
if @hmacKey
|
27
|
+
@message = ('9'*244) + @message
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid?
|
32
|
+
keysToValidate = [
|
33
|
+
{ key: 'address', validator: :isAddress, args: nil },
|
34
|
+
{ key: 'value', validator: :isValue, args: nil },
|
35
|
+
{ key: 'message', validator: :isTrytes, args: nil },
|
36
|
+
{ key: 'obsoleteTag', validator: :isTrytes, args: '0,27' }
|
37
|
+
]
|
38
|
+
|
39
|
+
validator = IOTA::Utils::ObjectValidator.new(keysToValidate)
|
40
|
+
validator.valid?(self)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Multisig
|
3
|
+
class Address
|
4
|
+
def initialize(digests = nil)
|
5
|
+
# Initialize kerl instance
|
6
|
+
@kerl = IOTA::Crypto::Kerl.new
|
7
|
+
|
8
|
+
# Add digests if passed
|
9
|
+
absorb(digests) if digests
|
10
|
+
end
|
11
|
+
|
12
|
+
def absorb(digest)
|
13
|
+
# Construct array
|
14
|
+
digests = digest.class == Array ? digest : [digest]
|
15
|
+
|
16
|
+
# Add digests
|
17
|
+
digests.each do |d|
|
18
|
+
# Get trits of digest
|
19
|
+
digestTrits = IOTA::Crypto::Converter.trits(d)
|
20
|
+
|
21
|
+
# Absorb
|
22
|
+
@kerl.absorb(digestTrits, 0, digestTrits.length)
|
23
|
+
end
|
24
|
+
|
25
|
+
self
|
26
|
+
end
|
27
|
+
|
28
|
+
def finalize(digest = nil)
|
29
|
+
# Absorb last digest if passed
|
30
|
+
absorb(digest) if digest
|
31
|
+
|
32
|
+
# Squeeze the address trits
|
33
|
+
addressTrits = []
|
34
|
+
@kerl.squeeze(addressTrits, 0, IOTA::Crypto::Kerl::HASH_LENGTH)
|
35
|
+
|
36
|
+
# Convert trits into trytes and return the address
|
37
|
+
IOTA::Crypto::Converter.trytes(addressTrits)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,244 @@
|
|
1
|
+
module IOTA
|
2
|
+
module Multisig
|
3
|
+
class Multisig
|
4
|
+
def initialize(client)
|
5
|
+
@api = client.api
|
6
|
+
@utils = client.utils
|
7
|
+
@validator = client.validator
|
8
|
+
@converter = IOTA::Crypto::Converter
|
9
|
+
@signing = IOTA::Crypto::Signing
|
10
|
+
end
|
11
|
+
|
12
|
+
def getKey(seed, index, security)
|
13
|
+
pk = getPrivateKey(seed, index, security)
|
14
|
+
@converter.trytes(pk.key)
|
15
|
+
end
|
16
|
+
|
17
|
+
def getDigest(seed, index, security)
|
18
|
+
pk = getPrivateKey(seed, index, security)
|
19
|
+
@converter.trytes(pk.digests)
|
20
|
+
end
|
21
|
+
|
22
|
+
def validateAddress(multisigAddress, digests)
|
23
|
+
kerl = IOTA::Crypto::Kerl.new
|
24
|
+
|
25
|
+
# Absorb all key digests
|
26
|
+
digests.each do |digest|
|
27
|
+
trits = @converter.trits(digest)
|
28
|
+
kerl.absorb(@converter.trits(digest), 0, trits.length)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Squeeze address trits
|
32
|
+
addressTrits = []
|
33
|
+
kerl.squeeze(addressTrits, 0, IOTA::Crypto::Kerl::HASH_LENGTH)
|
34
|
+
|
35
|
+
# Convert trits into trytes and return the address
|
36
|
+
@converter.trytes(addressTrits) === multisigAddress
|
37
|
+
end
|
38
|
+
|
39
|
+
def initiateTransfer(input, remainderAddress = nil, transfers)
|
40
|
+
input = IOTA::Models::Input.new(input) if input.class != IOTA::Models::Input
|
41
|
+
|
42
|
+
# If message or tag is not supplied, provide it
|
43
|
+
# Also remove the checksum of the address if it's there
|
44
|
+
(0...transfers.length).step(1) do |i|
|
45
|
+
transfers[i] = IOTA::Models::Transfer.new(transfers[i]) if transfers[i].class != IOTA::Models::Transfer
|
46
|
+
end
|
47
|
+
|
48
|
+
# Input validation of transfers object
|
49
|
+
raise StandardError, "Invalid transfers provided" if !@validator.isTransfersArray(transfers)
|
50
|
+
|
51
|
+
# check if int
|
52
|
+
raise StandardError, "Invalid inputs provided" if !@validator.isValue(input.security)
|
53
|
+
|
54
|
+
# validate input address
|
55
|
+
raise StandardError, "Invalid input address provided" if !@validator.isAddress(input.address)
|
56
|
+
|
57
|
+
# validate remainder address
|
58
|
+
raise StandardError, "Invalid remainder address provided" if remainderAddress && !@validator.isAddress(remainderAddress)
|
59
|
+
|
60
|
+
remainderAddress = @utils.noChecksum(remainderAddress) if remainderAddress && remainderAddress.length == 90
|
61
|
+
|
62
|
+
# Create a new bundle
|
63
|
+
bundle = IOTA::Crypto::Bundle.new
|
64
|
+
|
65
|
+
totalValue = 0
|
66
|
+
signatureFragments = []
|
67
|
+
tag = nil
|
68
|
+
|
69
|
+
# Iterate over all transfers, get totalValue and prepare the signatureFragments, message and tag
|
70
|
+
(0...transfers.length).step(1) do |i|
|
71
|
+
signatureMessageLength = 1
|
72
|
+
|
73
|
+
# If message longer than 2187 trytes, increase signatureMessageLength (add multiple transactions)
|
74
|
+
if transfers[i].message.length > 2187
|
75
|
+
# Get total length, message / maxLength (2187 trytes)
|
76
|
+
signatureMessageLength += (transfers[i].message.length / 2187).floor
|
77
|
+
|
78
|
+
msgCopy = transfers[i].message
|
79
|
+
|
80
|
+
# While there is still a message, copy it
|
81
|
+
while msgCopy
|
82
|
+
fragment = msgCopy.slice(0, 2187)
|
83
|
+
msgCopy = msgCopy.slice(2187, msgCopy.length)
|
84
|
+
|
85
|
+
# Pad remainder of fragment
|
86
|
+
fragment += (['9']*(2187-fragment.length)).join('') if fragment.length < 2187
|
87
|
+
|
88
|
+
signatureFragments.push(fragment)
|
89
|
+
end
|
90
|
+
else
|
91
|
+
# Else, get single fragment with 2187 of 9's trytes
|
92
|
+
fragment = ''
|
93
|
+
|
94
|
+
fragment = transfers[i].message.slice(0, 2187) if transfers[i].message
|
95
|
+
|
96
|
+
# Pad remainder of fragment
|
97
|
+
fragment += (['9']*(2187-fragment.length)).join('') if fragment.length < 2187
|
98
|
+
|
99
|
+
signatureFragments.push(fragment)
|
100
|
+
end
|
101
|
+
|
102
|
+
# get current timestamp in seconds
|
103
|
+
timestamp = Time.now.utc.to_i
|
104
|
+
|
105
|
+
# If no tag defined, get 27 tryte tag.
|
106
|
+
tag = transfers[i].obsoleteTag ? transfers[i].obsoleteTag : (['9']*27).join('')
|
107
|
+
|
108
|
+
# Pad for required 27 tryte length
|
109
|
+
tag += (['9']*(27-tag.length)).join('') if tag.length < 27
|
110
|
+
|
111
|
+
# Add first entries to the bundle
|
112
|
+
# Slice the address in case the user provided a checksummed one
|
113
|
+
bundle.addEntry(signatureMessageLength, transfers[i].address.slice(0, 81), transfers[i].value, tag, timestamp)
|
114
|
+
|
115
|
+
# Sum up total value
|
116
|
+
totalValue += transfers[i].value.to_i
|
117
|
+
end
|
118
|
+
|
119
|
+
# Get inputs if we are sending tokens
|
120
|
+
if totalValue > 0
|
121
|
+
if input.balance
|
122
|
+
return createBundle(input.balance, totalValue, bundle, input, remainderAddress, tag, signatureFragments)
|
123
|
+
else
|
124
|
+
@api.getBalances([input.address], 100) do |st1, balances|
|
125
|
+
if !st1
|
126
|
+
raise StandardError, "Error fetching balances: #{balances}"
|
127
|
+
else
|
128
|
+
return createBundle(balances[0].to_i, totalValue, bundle, input, remainderAddress, tag, signatureFragments)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
else
|
133
|
+
raise StandardError, "Invalid value transfer: the transfer does not require a signature."
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
def addSignature(bundleToSign, inputAddress, key)
|
138
|
+
bundleToSign = [bundleToSign] if bundleToSign.class != Array
|
139
|
+
bundle = IOTA::Crypto::Bundle.new(bundleToSign)
|
140
|
+
|
141
|
+
# Get the security used for the private key
|
142
|
+
# 1 security level = 2187 trytes
|
143
|
+
security = (key.length / 2187).to_i
|
144
|
+
|
145
|
+
# convert private key trytes into trits
|
146
|
+
key = @converter.trits(key)
|
147
|
+
|
148
|
+
# First get the total number of already signed transactions
|
149
|
+
# use that for the bundle hash calculation as well as knowing
|
150
|
+
# where to add the signature
|
151
|
+
numSignedTxs = 0
|
152
|
+
|
153
|
+
(0...bundle.bundle.length).step(1) do |i|
|
154
|
+
bundle.bundle[i] = IOTA::Models::Transaction.new(bundle.bundle[i]) if bundle.bundle[i].class != IOTA::Models::Transaction
|
155
|
+
if bundle.bundle[i].address === inputAddress
|
156
|
+
# If transaction is already signed, increase counter
|
157
|
+
if !@validator.isAllNine(bundle.bundle[i].signatureMessageFragment)
|
158
|
+
numSignedTxs += 1
|
159
|
+
else
|
160
|
+
# sign the transactions
|
161
|
+
bundleHash = bundle.bundle[i].bundle
|
162
|
+
|
163
|
+
# First 6561 trits for the firstFragment
|
164
|
+
firstFragment = key.slice(0, 6561)
|
165
|
+
|
166
|
+
# Get the normalized bundle hash
|
167
|
+
normalizedBundleHash = bundle.normalizedBundle(bundleHash)
|
168
|
+
normalizedBundleFragments = []
|
169
|
+
|
170
|
+
# Split hash into 3 fragments
|
171
|
+
(0...3).step(1) do |k|
|
172
|
+
normalizedBundleFragments[k] = normalizedBundleHash.slice(k * 27, 27)
|
173
|
+
end
|
174
|
+
|
175
|
+
# First bundle fragment uses 27 trytes
|
176
|
+
firstBundleFragment = normalizedBundleFragments[numSignedTxs % 3]
|
177
|
+
|
178
|
+
# Calculate the new signatureFragment with the first bundle fragment
|
179
|
+
firstSignedFragment = @signing.signatureFragment(firstBundleFragment, firstFragment)
|
180
|
+
|
181
|
+
# Convert signature to trytes and assign the new signatureFragment
|
182
|
+
bundle.bundle[i].signatureMessageFragment = @converter.trytes(firstSignedFragment)
|
183
|
+
|
184
|
+
(1...security).step(1) do |j|
|
185
|
+
# Next 6561 trits for the firstFragment
|
186
|
+
nextFragment = key.slice(6561 * j, 6561)
|
187
|
+
|
188
|
+
|
189
|
+
# Use the next 27 trytes
|
190
|
+
nextBundleFragment = normalizedBundleFragments[(numSignedTxs + j) % 3]
|
191
|
+
|
192
|
+
# Calculate the new signatureFragment with the first bundle fragment
|
193
|
+
nextSignedFragment = @signing.signatureFragment(nextBundleFragment, nextFragment)
|
194
|
+
|
195
|
+
# Convert signature to trytes and add new bundle entry at i + j position
|
196
|
+
# Assign the signature fragment
|
197
|
+
bundle.bundle[i + j].signatureMessageFragment = @converter.trytes(nextSignedFragment)
|
198
|
+
end
|
199
|
+
|
200
|
+
break
|
201
|
+
end
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
bundle.bundle
|
206
|
+
end
|
207
|
+
|
208
|
+
private
|
209
|
+
def getPrivateKey(seed, index, security)
|
210
|
+
seed = IOTA::Models::Seed.new(seed) if seed.class != IOTA::Models::Seed
|
211
|
+
IOTA::Crypto::PrivateKey.new(seed.as_trits, index, security)
|
212
|
+
end
|
213
|
+
|
214
|
+
def createBundle(totalBalance, totalValue, bundle, input, remainderAddress, tag, signatureFragments)
|
215
|
+
if totalBalance > 0
|
216
|
+
toSubtract = 0 - totalBalance
|
217
|
+
timestamp = Time.now.utc.to_i
|
218
|
+
|
219
|
+
# Add input as bundle entry
|
220
|
+
# Only a single entry, signatures will be added later
|
221
|
+
bundle.addEntry(input.security, input.address, toSubtract, tag, timestamp)
|
222
|
+
end
|
223
|
+
|
224
|
+
raise StandardError, "Not enough balance" if totalValue > totalBalance
|
225
|
+
|
226
|
+
# If there is a remainder value
|
227
|
+
# Add extra output to send remaining funds to
|
228
|
+
if totalBalance > totalValue
|
229
|
+
remainder = totalBalance - totalValue
|
230
|
+
|
231
|
+
# Remainder bundle entry if necessary
|
232
|
+
return StandardError, "No remainder address defined" if !remainderAddress
|
233
|
+
|
234
|
+
bundle.addEntry(1, remainderAddress, remainder, tag, timestamp)
|
235
|
+
end
|
236
|
+
|
237
|
+
bundle.finalize()
|
238
|
+
bundle.addTrytes(signatureFragments)
|
239
|
+
|
240
|
+
bundle.bundle
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|