monacoin-openassets-ruby 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
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