monacoin-openassets-ruby 0.1.3

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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +2 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/.travis.yml +11 -0
  7. data/CODE_OF_CONDUCT.md +13 -0
  8. data/Gemfile +4 -0
  9. data/LICENSE +23 -0
  10. data/README.md +420 -0
  11. data/Rakefile +5 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +7 -0
  14. data/exe/monacoin-openassets +66 -0
  15. data/lib/monacoin-openassets.rb +23 -0
  16. data/lib/openassets/api.rb +423 -0
  17. data/lib/openassets/cache.rb +8 -0
  18. data/lib/openassets/cache/output_cache.rb +43 -0
  19. data/lib/openassets/cache/sqlite_base.rb +26 -0
  20. data/lib/openassets/cache/ssl_certificate_cache.rb +44 -0
  21. data/lib/openassets/cache/transaction_cache.rb +33 -0
  22. data/lib/openassets/error.rb +5 -0
  23. data/lib/openassets/medhod_filter.rb +55 -0
  24. data/lib/openassets/protocol.rb +10 -0
  25. data/lib/openassets/protocol/asset_definition.rb +118 -0
  26. data/lib/openassets/protocol/asset_definition_loader.rb +43 -0
  27. data/lib/openassets/protocol/http_asset_definition_loader.rb +27 -0
  28. data/lib/openassets/protocol/marker_output.rb +128 -0
  29. data/lib/openassets/protocol/output_type.rb +27 -0
  30. data/lib/openassets/protocol/transaction_output.rb +157 -0
  31. data/lib/openassets/provider.rb +7 -0
  32. data/lib/openassets/provider/api_error.rb +9 -0
  33. data/lib/openassets/provider/bitcoin_core_provider.rb +123 -0
  34. data/lib/openassets/provider/block_chain_provider_base.rb +22 -0
  35. data/lib/openassets/send_asset_param.rb +17 -0
  36. data/lib/openassets/send_bitcoin_param.rb +13 -0
  37. data/lib/openassets/transaction.rb +12 -0
  38. data/lib/openassets/transaction/dust_output_error.rb +10 -0
  39. data/lib/openassets/transaction/insufficient_asset_quantity_error.rb +7 -0
  40. data/lib/openassets/transaction/insufficient_funds_error.rb +10 -0
  41. data/lib/openassets/transaction/out_point.rb +22 -0
  42. data/lib/openassets/transaction/spendable_output.rb +38 -0
  43. data/lib/openassets/transaction/transaction_build_error.rb +9 -0
  44. data/lib/openassets/transaction/transaction_builder.rb +319 -0
  45. data/lib/openassets/transaction/transfer_parameters.rb +41 -0
  46. data/lib/openassets/util.rb +173 -0
  47. data/lib/openassets/version.rb +3 -0
  48. data/openassets-ruby.gemspec +38 -0
  49. metadata +246 -0
@@ -0,0 +1,38 @@
1
+ module OpenAssets
2
+ module Transaction
3
+
4
+ # A transaction output with information about the asset ID and asset quantity associated to it.
5
+ class SpendableOutput
6
+
7
+ # An object that can be used to locate the output.
8
+ attr_accessor :out_point
9
+ # The actual output object.
10
+ attr_accessor :output
11
+
12
+ attr_accessor :confirmations
13
+ attr_accessor :spendable
14
+ attr_accessor :solvable
15
+
16
+ # @param [OpenAssets::Transaction::OutPoint] out_point
17
+ # @param [OpenAssets::Protocol::TransactionOutput] output
18
+ def initialize(out_point, output)
19
+ @out_point = out_point
20
+ @output = output
21
+ @confirmations = nil
22
+ @solvable = nil
23
+ @spendable = nil
24
+ end
25
+
26
+ # convert to hash.
27
+ def to_hash
28
+ return {} if @output.nil?
29
+ h = {'txid' => @out_point.hash, 'vout' => @out_point.index, 'confirmations' => @confirmations}.merge(@output.to_hash)
30
+ h['solvable'] = @solvable unless @solvable.nil?
31
+ h['spendable'] = @spendable unless @spendable.nil?
32
+ h
33
+ end
34
+
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,9 @@
1
+ module OpenAssets
2
+ module Transaction
3
+
4
+ class TransactionBuildError < OpenAssets::Error
5
+
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,319 @@
1
+ module OpenAssets
2
+ module Transaction
3
+
4
+ class TransactionBuilder
5
+ include OpenAssets::Util
6
+
7
+ # The minimum allowed output value.
8
+ attr_accessor :amount
9
+
10
+ # The estimated transaction fee rate (satoshis/KB).
11
+ attr_accessor :efr
12
+
13
+ def initialize(amount = 600, efr = 10000)
14
+ @amount = amount
15
+ @efr = efr
16
+ end
17
+
18
+ # Creates a transaction for issuing an asset.
19
+ # @param [TransferParameters] issue_spec The parameters of the issuance.
20
+ # @param [bytes] metadata The metadata to be embedded in the transaction.
21
+ # @param [Integer] fees The fees to include in the transaction.
22
+ # @return[Bitcoin:Protocol:Tx] An unsigned transaction for issuing asset.
23
+ def issue_asset(issue_spec, metadata, fees)
24
+
25
+ if fees == :auto
26
+ # Calculate fees (assume that one vin and four vouts are wrote)
27
+ fees = calc_fee(1, 4)
28
+ end
29
+
30
+ inputs, total_amount =
31
+ TransactionBuilder.collect_uncolored_outputs(issue_spec.unspent_outputs, 2 * @amount + fees)
32
+ tx = Bitcoin::Protocol::Tx.new
33
+ inputs.each { |spendable|
34
+ script_sig = spendable.output.script.to_binary
35
+ tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(spendable.out_point.hash, spendable.out_point.index)
36
+ tx_in.script_sig = script_sig
37
+ tx.add_in(tx_in)
38
+ }
39
+ issue_address = oa_address_to_address(issue_spec.to_script)
40
+ from_address = oa_address_to_address(issue_spec.change_script)
41
+ validate_address([issue_address, from_address])
42
+ asset_quantities =[]
43
+ issue_spec.split_output_amount.each{|amount|
44
+ asset_quantities << amount
45
+ tx.add_out(create_colored_output(issue_address))
46
+ }
47
+ tx.add_out(create_marker_output(asset_quantities, metadata))
48
+ tx.add_out(create_uncolored_output(from_address, total_amount - @amount - fees))
49
+ tx
50
+ end
51
+
52
+ # Creates a transaction for sending an asset.
53
+ # @param[String] asset_id The ID of the asset being sent.
54
+ # @param[OpenAssets::Transaction::TransferParameters] asset_transfer_spec The parameters of the asset being transferred.
55
+ # @param[String] btc_change_script The script where to send bitcoin change, if any.
56
+ # @param[Integer] fees The fees to include in the transaction.
57
+ # @return[Bitcoin::Protocol:Tx] The resulting unsigned transaction.
58
+ def transfer_asset(asset_id, asset_transfer_spec, btc_change_script, fees)
59
+ btc_transfer_spec = OpenAssets::Transaction::TransferParameters.new(
60
+ asset_transfer_spec.unspent_outputs, nil, oa_address_to_address(btc_change_script), 0)
61
+ transfer([[asset_id, asset_transfer_spec]], [btc_transfer_spec], fees)
62
+ end
63
+
64
+ # Creates a transaction for sending assets to many.
65
+ # @param[Array[OpenAssets::Transaction::TransferParameters]] asset_transfer_spec The parameters of the asset being transferred.
66
+ # @param[OpenAssets::Transaction::TransferParameters] btc_transfer_spec The script where to send bitcoin change, if any.
67
+ # @param[Integer] fees The fees to include in the transaction.
68
+ # @return[Bitcoin::Protocol:Tx] The resulting unsigned transaction.
69
+ def transfer_assets(transfer_specs, btc_transfer_spec, fees)
70
+ transfer(transfer_specs, [btc_transfer_spec], fees)
71
+ end
72
+
73
+ # Creates a transaction for sending bitcoins.
74
+ # @param[OpenAssets::Transaction::TransferParameters] btc_transfer_spec The parameters of the bitcoins being transferred.
75
+ # @param[Integer] fees The fees to include in the transaction.
76
+ # @return[Bitcoin::Protocol:Tx] The resulting unsigned transaction.
77
+ def transfer_btc(btc_transfer_spec, fees)
78
+ transfer_btcs([btc_transfer_spec], fees)
79
+ end
80
+
81
+ # Creates a transaction for sending bitcoins to many.
82
+ # @param[Array[OpenAssets::Transaction::TransferParameters]] btc_transfer_specs The parameters of the bitcoins being transferred.
83
+ # @param[Integer] fees The fees to include in the transaction.
84
+ # @return[Bitcoin::Protocol:Tx] The resulting unsigned transaction.
85
+ def transfer_btcs(btc_transfer_specs, fees)
86
+ transfer([], btc_transfer_specs, fees)
87
+ end
88
+
89
+ # Create a transaction for burn asset
90
+ def burn_asset(unspents, asset_id, fee)
91
+
92
+ tx = Bitcoin::Protocol::Tx.new
93
+ targets = unspents.select{|o|o.output.asset_id == asset_id}
94
+
95
+ if fee == :auto
96
+ # Calculate fees and otsuri (assume that one vout exists)
97
+ fee = calc_fee(targets.size, 1)
98
+ end
99
+
100
+ raise TransactionBuildError.new('There is no asset.') if targets.length == 0
101
+ total_amount = targets.inject(0){|sum, o|o.output.value + sum}
102
+ otsuri = total_amount - fee
103
+ if otsuri < @amount
104
+ uncolored_outputs, uncolored_amount =
105
+ TransactionBuilder.collect_uncolored_outputs(unspents, @amount - otsuri)
106
+ targets = targets + uncolored_outputs
107
+ otsuri += uncolored_amount
108
+ end
109
+ targets.each{|o|
110
+ script_sig = o.output.script.to_binary
111
+ tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(o.out_point.hash, o.out_point.index)
112
+ tx_in.script_sig = script_sig
113
+ tx.add_in(tx_in)
114
+ }
115
+ tx.add_out(create_uncolored_output(targets[0].output.address, otsuri))
116
+ tx
117
+ end
118
+
119
+ # collect uncolored outputs in unspent outputs(contains colored output).
120
+ # @param [Array[OpenAssets::Transaction::SpendableOutput]] unspent_outputs The Array of available outputs.
121
+ # @param [Integer] amount The amount to collect.
122
+ # @return [Array] inputs, total_amount
123
+ def self.collect_uncolored_outputs(unspent_outputs, amount)
124
+ total_amount = 0
125
+ results = []
126
+ unspent_outputs.each do |output|
127
+ if output.output.asset_id.nil?
128
+ results << output
129
+ total_amount += output.output.value
130
+ end
131
+ return results, total_amount if total_amount >= amount
132
+ end
133
+ raise InsufficientFundsError
134
+ end
135
+
136
+ # Returns a list of colored outputs for the specified quantity.
137
+ # @param[Array[OpenAssets::Transaction::SpendableOutput]] unspent_outputs
138
+ # @param[String] asset_id The ID of the asset to collect.
139
+ # @param[Integer] asset_quantity The asset quantity to collect.
140
+ # @return[Array[OpenAssets::Transaction::SpendableOutput], int] A list of outputs, and the total asset quantity collected.
141
+ def self.collect_colored_outputs(unspent_outputs, asset_id, asset_quantity)
142
+ total_amount = 0
143
+ result = []
144
+ unspent_outputs.each do |output|
145
+ if output.output.asset_id == asset_id
146
+ result << output
147
+ total_amount += output.output.asset_quantity
148
+ end
149
+ return result, total_amount if total_amount >= asset_quantity
150
+ end
151
+ raise InsufficientAssetQuantityError
152
+ end
153
+
154
+ private
155
+ # create colored output.
156
+ # @param [String] address The Bitcoin address.
157
+ # @return [Bitcoin::Protocol::TxOut] colored output
158
+ def create_colored_output(address)
159
+ Bitcoin::Protocol::TxOut.new(@amount, Bitcoin::Script.new(Bitcoin::Script.to_address_script(address)).to_payload)
160
+ end
161
+
162
+ # create marker output.
163
+ # @param [Array] asset_quantities asset_quantity array.
164
+ # @param [String] metadata
165
+ # @return [Bitcoin::Protocol::TxOut] the marker output.
166
+ def create_marker_output(asset_quantities, metadata = '')
167
+ script = OpenAssets::Protocol::MarkerOutput.new(asset_quantities, metadata).build_script
168
+ Bitcoin::Protocol::TxOut.new(0, script.to_payload)
169
+ end
170
+
171
+ # create an uncolored output.
172
+ # @param [String] address: The Bitcoin address.
173
+ # @param [Integer] value: The satoshi value of the output.
174
+ # @return [Bitcoin::Protocol::TxOut] an uncolored output.
175
+ def create_uncolored_output(address, value)
176
+ raise DustOutputError if value < @amount
177
+ Bitcoin::Protocol::TxOut.new(value, Bitcoin::Script.new(Bitcoin::Script.to_address_script(address)).to_payload)
178
+ end
179
+
180
+
181
+ # create a transaction
182
+ # @param[Array[OpenAssets::Transaction::TransferParameters]] asset_transfer_specs The parameters of the assets being transferred.
183
+ # @param[Array[OpenAssets::Transaction::TransferParameters]] btc_transfer_specs The parameters of the bitcoins being transferred.
184
+ # @param[Integer] fees The fees to include in the transaction.
185
+ # @return[Bitcoin::Protocol:Tx] The resulting unsigned transaction.
186
+ def transfer(asset_transfer_specs, btc_transfer_specs, fees)
187
+ inputs = [] # vin field
188
+ outputs = [] # vout field
189
+ asset_quantities = []
190
+
191
+ # Only when assets are transferred
192
+ asset_based_specs = {}
193
+ asset_transfer_specs.each{|asset_id, transfer_spec|
194
+ asset_based_specs[asset_id] = {} unless asset_based_specs.has_key?(asset_id)
195
+ asset_based_specs[asset_id][transfer_spec.change_script] = [] unless asset_based_specs[asset_id].has_key?(transfer_spec.change_script)
196
+ asset_based_specs[asset_id][transfer_spec.change_script] << transfer_spec
197
+ }
198
+
199
+ asset_based_specs.each{|asset_id, address_based_specs|
200
+ address_based_specs.values.each{|transfer_specs|
201
+ transfer_amount = transfer_specs.inject(0){|sum, s| sum + s.amount}
202
+ colored_outputs, total_amount = TransactionBuilder.collect_colored_outputs(transfer_specs[0].unspent_outputs, asset_id, transfer_amount)
203
+ inputs = inputs + colored_outputs
204
+ transfer_specs.each{|spec|
205
+ # add asset transfer output
206
+ spec.split_output_amount.each {|amount|
207
+ outputs << create_colored_output(oa_address_to_address(spec.to_script))
208
+ asset_quantities << amount
209
+ }
210
+ }
211
+ # add the rest of the asset to the origin address
212
+ if total_amount > transfer_amount
213
+ outputs << create_colored_output(oa_address_to_address(transfer_specs[0].change_script))
214
+ asset_quantities << (total_amount - transfer_amount)
215
+ end
216
+ }
217
+ }
218
+ # End of asset settings
219
+
220
+ ## For bitcoins transfer
221
+ # Assume that there is one address from
222
+ utxo = btc_transfer_specs[0].unspent_outputs.dup
223
+
224
+ # Calculate rest of bitcoins in asset settings
225
+ # btc_excess = inputs(colored) total satoshi - outputs(transfer) total satoshi
226
+ btc_excess = inputs.inject(0) { |sum, i| sum + i.output.value } - outputs.inject(0){|sum, o| sum + o.value}
227
+
228
+ # Calculate total amount of bitcoins to send
229
+ btc_transfer_total_amount = btc_transfer_specs.inject(0) {|sum, b| sum + b.amount}
230
+
231
+ if fees == :auto
232
+ fixed_fees = 0
233
+ else
234
+ fixed_fees = fees
235
+ end
236
+
237
+ if btc_excess < btc_transfer_total_amount + fixed_fees
238
+ # When there does not exist enough bitcoins to send in the inputs
239
+ # assign new address (utxo) to the inputs (does not include output coins)
240
+ # CREATING INPUT (if needed)
241
+ uncolored_outputs, uncolored_amount =
242
+ TransactionBuilder.collect_uncolored_outputs(utxo, btc_transfer_total_amount + fixed_fees - btc_excess)
243
+ utxo = utxo - uncolored_outputs
244
+ inputs << uncolored_outputs
245
+ btc_excess += uncolored_amount
246
+ end
247
+
248
+ if fees == :auto
249
+ # Calculate fees and otsuri (the numbers of vins and vouts are known)
250
+ # +1 in the second term means "otsuri" vout,
251
+ # and outputs size means the number of vout witn asset_id
252
+ fees = calc_fee(inputs.size, outputs.size + btc_transfer_specs.size + 1)
253
+ end
254
+
255
+ otsuri = btc_excess - btc_transfer_total_amount - fees
256
+
257
+ if otsuri > 0 && otsuri < @amount
258
+ # When there exists otsuri, but it is smaller than @amount (default is 600 satoshis)
259
+ # assign new address (utxo) to the input (does not include @amount - otsuri)
260
+ # CREATING INPUT (if needed)
261
+ uncolored_outputs, uncolored_amount =
262
+ TransactionBuilder.collect_uncolored_outputs(utxo, @amount - otsuri)
263
+ inputs << uncolored_outputs
264
+ otsuri += uncolored_amount
265
+ end
266
+
267
+ if otsuri > 0
268
+ # When there exists otsuri, write it to outputs
269
+ # CREATING OUTPUT
270
+ outputs << create_uncolored_output(btc_transfer_specs[0].change_script, otsuri)
271
+ end
272
+
273
+ btc_transfer_specs.each{|btc_transfer_spec|
274
+ if btc_transfer_spec.amount > 0
275
+ # Write output for bitcoin transfer by specifics of the argument
276
+ # CREATING OUTPUT
277
+ btc_transfer_spec.split_output_amount.each {|amount|
278
+ outputs << create_uncolored_output(btc_transfer_spec.to_script, amount)
279
+ }
280
+ end
281
+ }
282
+
283
+ # add marker output to outputs first.
284
+ unless asset_quantities.empty?
285
+ outputs.unshift(create_marker_output(asset_quantities))
286
+ end
287
+
288
+ # create a bitcoin transaction (assembling inputs and output into one transaction)
289
+ tx = Bitcoin::Protocol::Tx.new
290
+ # for all inputs (vin fields), add sigs to the same transaction
291
+ inputs.flatten.each{|i|
292
+ script_sig = i.output.script.to_binary
293
+ tx_in = Bitcoin::Protocol::TxIn.from_hex_hash(i.out_point.hash, i.out_point.index)
294
+ tx_in.script_sig = script_sig
295
+ tx.add_in(tx_in)
296
+ }
297
+
298
+ outputs.each{|o|tx.add_out(o)}
299
+
300
+ tx
301
+ end
302
+
303
+ # Calculate a transaction fee
304
+ # @param [Integer] inputs_num: The number of vin fields.
305
+ # @param [Integer] outputs_num: The number of vout fields.
306
+ # @return [Integer] A transaction fee.
307
+ def calc_fee(inputs_num, outputs_num)
308
+ # See http://bitcoinfees.com/
309
+ tx_size = 148 * inputs_num + 34 * outputs_num + 10
310
+ # See Bitcoin::tx.calculate_minimum_fee
311
+ fee = (1 + tx_size / 1_000) * @efr
312
+
313
+ fee
314
+ end
315
+
316
+ end
317
+
318
+ end
319
+ end
@@ -0,0 +1,41 @@
1
+ module OpenAssets
2
+ module Transaction
3
+
4
+ # The value object of a bitcoin or asset transfer.
5
+ class TransferParameters
6
+
7
+ attr_accessor :unspent_outputs
8
+ attr_accessor :amount
9
+ attr_accessor :change_script
10
+ attr_accessor :to_script
11
+ attr_accessor :output_qty
12
+
13
+ # initialize
14
+ # @param [Array[OpenAssets::Transaction::SpendableOutput]] unspent_outputs Array of the unspent outputs available for the transaction.
15
+ # @param [String] to_script the output script to which to send the assets or bitcoins.
16
+ # @param [String] change_script the output script to which to send any remaining change.
17
+ # @param [Integer] amount The asset quantity or amount of the satoshi sent in the transaction.
18
+ def initialize(unspent_outputs, to_script, change_script, amount, output_qty = 1)
19
+ @unspent_outputs = unspent_outputs
20
+ @to_script = to_script
21
+ @change_script = change_script
22
+ @amount = amount
23
+ @output_qty = output_qty
24
+ end
25
+
26
+ def split_output_amount
27
+ split_amounts = []
28
+ output_qty.times{|index|
29
+ if index == output_qty - 1
30
+ value = amount / output_qty + amount % output_qty
31
+ else
32
+ value = amount / output_qty
33
+ end
34
+ split_amounts << value
35
+ }
36
+ split_amounts
37
+ end
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,173 @@
1
+ require 'bigdecimal'
2
+ module OpenAssets
3
+ module Util
4
+ extend ::Bitcoin::Util
5
+ include ::Bitcoin::Util
6
+
7
+ # namespace of Open Asset
8
+ OA_NAMESPACE = 19
9
+
10
+ # version byte for Open Assets Address
11
+ OA_VERSION_BYTE = 23
12
+ OA_VERSION_BYTE_TESTNET = 115
13
+
14
+ # convert bitcoin address to open assets address
15
+ # @param [String] btc_address The Bitcoin address.
16
+ # @return [String] The Open Assets Address.
17
+ def address_to_oa_address(btc_address)
18
+ btc_hex = decode_base58(btc_address)
19
+ btc_hex = '0' +btc_hex if btc_hex.size==47
20
+ address = btc_hex[0..-9] # bitcoin address without checksum
21
+ named_addr = OA_NAMESPACE.to_s(16) + address
22
+ oa_checksum = checksum(named_addr)
23
+ encode_base58(named_addr + oa_checksum)
24
+ end
25
+
26
+ # convert open assets address to bitcoin address
27
+ # @param [String] oa_address The Open Assets Address.
28
+ # @return [String] The Bitcoin address.
29
+ def oa_address_to_address(oa_address)
30
+ decode_address = decode_base58(oa_address)
31
+ btc_addr = decode_address[2..-9]
32
+ btc_checksum = checksum(btc_addr)
33
+ encode_base58(btc_addr + btc_checksum)
34
+ end
35
+
36
+ # generate asset ID from public key.
37
+ def generate_asset_id(pub_key)
38
+ pubkey_hash_to_asset_id(hash160(pub_key))
39
+ end
40
+
41
+ def pubkey_hash_to_asset_id(pubkey_hash)
42
+ # gen P2PKH script hash
43
+ # P2PKH script = OP_DUP OP_HASH160 <PubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
44
+ # (76=OP_DUP, a9=OP_HASH160, 14=Bytes to push, 88=OP_EQUALVERIFY, ac=OP_CHECKSIG)
45
+ hash_to_asset_id(hash160(["76", "a9", "14", pubkey_hash, "88", "ac"].join))
46
+ end
47
+
48
+ def script_to_asset_id(script)
49
+ hash_to_asset_id(hash160(script))
50
+ end
51
+
52
+ def hash_to_asset_id(hash)
53
+ hash = oa_version_byte.to_s(16) + hash # add version byte to script hash
54
+ encode_base58(hash + checksum(hash)) # add checksum & encode
55
+ end
56
+
57
+ # LEB128 encode
58
+ def encode_leb128(value)
59
+ LEB128.encode_unsigned(value).read.bth
60
+ end
61
+
62
+ # LEB128 decode
63
+ def decode_leb128(value)
64
+ results = []
65
+ sio = StringIO.new
66
+ value.htb.each_byte{|b|
67
+ sio.putc(b)
68
+ if b < 128
69
+ results << LEB128.decode_unsigned(sio)
70
+ sio = StringIO.new
71
+ end
72
+ }
73
+ results
74
+ end
75
+
76
+ def to_bytes(string)
77
+ string.each_char.each_slice(2).map{|v|v.join}
78
+ end
79
+
80
+ # Convert satoshi to coin.
81
+ # @param [Integer] satoshi The amount of satoshi unit.
82
+ # @return [String] The amount of coin.
83
+ def satoshi_to_coin(satoshi)
84
+ "%.8f" % (satoshi / 100000000.0)
85
+ end
86
+
87
+ # Convert coin unit to satoshi.
88
+ # @param [String] coin The amount of bitcoin
89
+ # @return [String] The amount of satoshi.
90
+ def coin_to_satoshi(coin)
91
+ BigDecimal(coin) * BigDecimal(100000000)
92
+ end
93
+
94
+ # Get address from script.
95
+ # @param [Bitcoin::Script] script The output script.
96
+ # @return [String] The Bitcoin address. if the script dose not contains address, return nil.
97
+ def script_to_address(script)
98
+ return script.get_pubkey_address if script.is_pubkey?
99
+ return script.get_hash160_address if script.is_hash160?
100
+ return script.get_multisig_addresses if script.is_multisig?
101
+ return script.get_p2sh_address if script.is_p2sh?
102
+ nil
103
+ end
104
+
105
+ # validate bitcoin address
106
+ def validate_address(addresses)
107
+ addresses.each{|a|
108
+ raise ArgumentError, "#{a} is invalid bitcoin address. " unless valid_address?(a)
109
+ }
110
+ end
111
+
112
+ # validate asset ID
113
+ def valid_asset_id?(asset_id)
114
+ return false if asset_id.nil? || asset_id.length != 34
115
+ decoded = decode_base58(asset_id)
116
+ return false if decoded[0,2].to_i(16) != oa_version_byte
117
+ p2pkh_script_hash = decoded[2..-9]
118
+ address = hash160_to_address(p2pkh_script_hash)
119
+ valid_address?(address)
120
+ end
121
+
122
+ # read variable integer
123
+ # @param [String] data reading data
124
+ # @param [Integer] offset the position when reading from data.
125
+ # @return [[Integer, Integer]] decoded integer value and the reading byte length.
126
+ # https://en.bitcoin.it/wiki/Protocol_documentation#Variable_length_integer
127
+ def read_var_integer(data, offset = 0)
128
+ raise ArgumentError, "data is nil." unless data
129
+ packed = [data].pack('H*')
130
+ return [nil, 0] if packed.bytesize < 1+offset
131
+ bytes = packed.bytes[offset..(offset + 9)] # 9 is variable integer max storage length.
132
+ first_byte = bytes[0]
133
+ if first_byte < 0xfd
134
+ [first_byte, offset + 1]
135
+ elsif first_byte == 0xfd
136
+ [calc_var_integer_val(bytes[1..2]), offset + 3]
137
+ elsif first_byte == 0xfe
138
+ [calc_var_integer_val(bytes[1..4]), offset + 5]
139
+ elsif first_byte == 0xff
140
+ [calc_var_integer_val(bytes[1..8]), offset + 9]
141
+ end
142
+ end
143
+
144
+ # read leb128 value
145
+ # @param [String] data reading data
146
+ # @param [Integer] offset start position when reading from data.
147
+ # @return [[Integer, Integer]] decoded integer value and the reading byte length.
148
+ def read_leb128(data, offset = 0)
149
+ bytes = [data].pack('H*').bytes
150
+ result = 0
151
+ shift = 0
152
+ while true
153
+ return [nil, offset] if bytes.length < 1 + offset
154
+ byte = bytes[offset..(offset + 1)][0]
155
+ result |= (byte & 0x7f) << shift
156
+ break if byte & 0x80 == 0
157
+ shift += 7
158
+ offset += 1
159
+ end
160
+ [result, offset + 1]
161
+ end
162
+
163
+ private
164
+ def oa_version_byte
165
+ Bitcoin.network[:address_version] == "6f" ? OA_VERSION_BYTE_TESTNET : OA_VERSION_BYTE
166
+ end
167
+
168
+ def calc_var_integer_val(byte_array)
169
+ byte_array.each_with_index.inject(0){|sum, pair| pair[1] == 0 ? pair[0] : sum + pair[0]*(256**pair[1])}
170
+ end
171
+
172
+ end
173
+ end