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.
- 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
|