monacoin-openassets-ruby 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/.travis.yml +11 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE +23 -0
- data/README.md +420 -0
- data/Rakefile +5 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/exe/monacoin-openassets +66 -0
- data/lib/monacoin-openassets.rb +23 -0
- data/lib/openassets/api.rb +423 -0
- data/lib/openassets/cache.rb +8 -0
- data/lib/openassets/cache/output_cache.rb +43 -0
- data/lib/openassets/cache/sqlite_base.rb +26 -0
- data/lib/openassets/cache/ssl_certificate_cache.rb +44 -0
- data/lib/openassets/cache/transaction_cache.rb +33 -0
- data/lib/openassets/error.rb +5 -0
- data/lib/openassets/medhod_filter.rb +55 -0
- data/lib/openassets/protocol.rb +10 -0
- data/lib/openassets/protocol/asset_definition.rb +118 -0
- data/lib/openassets/protocol/asset_definition_loader.rb +43 -0
- data/lib/openassets/protocol/http_asset_definition_loader.rb +27 -0
- data/lib/openassets/protocol/marker_output.rb +128 -0
- data/lib/openassets/protocol/output_type.rb +27 -0
- data/lib/openassets/protocol/transaction_output.rb +157 -0
- data/lib/openassets/provider.rb +7 -0
- data/lib/openassets/provider/api_error.rb +9 -0
- data/lib/openassets/provider/bitcoin_core_provider.rb +123 -0
- data/lib/openassets/provider/block_chain_provider_base.rb +22 -0
- data/lib/openassets/send_asset_param.rb +17 -0
- data/lib/openassets/send_bitcoin_param.rb +13 -0
- data/lib/openassets/transaction.rb +12 -0
- data/lib/openassets/transaction/dust_output_error.rb +10 -0
- data/lib/openassets/transaction/insufficient_asset_quantity_error.rb +7 -0
- data/lib/openassets/transaction/insufficient_funds_error.rb +10 -0
- data/lib/openassets/transaction/out_point.rb +22 -0
- data/lib/openassets/transaction/spendable_output.rb +38 -0
- data/lib/openassets/transaction/transaction_build_error.rb +9 -0
- data/lib/openassets/transaction/transaction_builder.rb +319 -0
- data/lib/openassets/transaction/transfer_parameters.rb +41 -0
- data/lib/openassets/util.rb +173 -0
- data/lib/openassets/version.rb +3 -0
- data/openassets-ruby.gemspec +38 -0
- 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,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
|