btcruby 0.0.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +18 -0
  3. data/.travis.yml +7 -0
  4. data/FAQ.md +7 -0
  5. data/Gemfile +3 -0
  6. data/Gemfile.lock +18 -0
  7. data/HOWTO.md +17 -0
  8. data/LICENSE +19 -0
  9. data/README.md +59 -0
  10. data/Rakefile +6 -0
  11. data/TODO.txt +40 -0
  12. data/bin/console +19 -0
  13. data/btcruby.gemspec +20 -0
  14. data/documentation/address.md +73 -0
  15. data/documentation/base58.md +52 -0
  16. data/documentation/block.md +127 -0
  17. data/documentation/block_header.md +120 -0
  18. data/documentation/constants.md +88 -0
  19. data/documentation/data.md +54 -0
  20. data/documentation/diagnostics.md +90 -0
  21. data/documentation/extensions.md +76 -0
  22. data/documentation/hash_functions.md +58 -0
  23. data/documentation/hash_id.md +22 -0
  24. data/documentation/index.md +230 -0
  25. data/documentation/key.md +177 -0
  26. data/documentation/keychain.md +180 -0
  27. data/documentation/network.md +75 -0
  28. data/documentation/opcode.md +220 -0
  29. data/documentation/openssl.md +7 -0
  30. data/documentation/p2pkh.md +71 -0
  31. data/documentation/p2sh.md +64 -0
  32. data/documentation/proof_of_work.md +84 -0
  33. data/documentation/script.md +280 -0
  34. data/documentation/signature.md +71 -0
  35. data/documentation/transaction.md +213 -0
  36. data/documentation/transaction_builder.md +188 -0
  37. data/documentation/transaction_input.md +133 -0
  38. data/documentation/transaction_output.md +130 -0
  39. data/documentation/wif.md +72 -0
  40. data/documentation/wire_format.md +70 -0
  41. data/lib/btcruby/address.rb +296 -0
  42. data/lib/btcruby/base58.rb +108 -0
  43. data/lib/btcruby/big_number.rb +47 -0
  44. data/lib/btcruby/block.rb +170 -0
  45. data/lib/btcruby/block_header.rb +231 -0
  46. data/lib/btcruby/constants.rb +59 -0
  47. data/lib/btcruby/currency_formatter.rb +64 -0
  48. data/lib/btcruby/data.rb +98 -0
  49. data/lib/btcruby/diagnostics.rb +92 -0
  50. data/lib/btcruby/errors.rb +8 -0
  51. data/lib/btcruby/extensions.rb +65 -0
  52. data/lib/btcruby/hash_functions.rb +54 -0
  53. data/lib/btcruby/hash_id.rb +18 -0
  54. data/lib/btcruby/key.rb +517 -0
  55. data/lib/btcruby/keychain.rb +464 -0
  56. data/lib/btcruby/network.rb +73 -0
  57. data/lib/btcruby/opcode.rb +197 -0
  58. data/lib/btcruby/open_assets/asset.rb +35 -0
  59. data/lib/btcruby/open_assets/asset_address.rb +49 -0
  60. data/lib/btcruby/open_assets/asset_definition.rb +75 -0
  61. data/lib/btcruby/open_assets/asset_id.rb +24 -0
  62. data/lib/btcruby/open_assets/asset_marker.rb +94 -0
  63. data/lib/btcruby/open_assets/asset_processor.rb +377 -0
  64. data/lib/btcruby/open_assets/asset_transaction.rb +184 -0
  65. data/lib/btcruby/open_assets/asset_transaction_builder/errors.rb +15 -0
  66. data/lib/btcruby/open_assets/asset_transaction_builder/provider.rb +32 -0
  67. data/lib/btcruby/open_assets/asset_transaction_builder/result.rb +47 -0
  68. data/lib/btcruby/open_assets/asset_transaction_builder.rb +418 -0
  69. data/lib/btcruby/open_assets/asset_transaction_input.rb +64 -0
  70. data/lib/btcruby/open_assets/asset_transaction_output.rb +140 -0
  71. data/lib/btcruby/open_assets.rb +26 -0
  72. data/lib/btcruby/openssl.rb +536 -0
  73. data/lib/btcruby/proof_of_work.rb +110 -0
  74. data/lib/btcruby/safety.rb +26 -0
  75. data/lib/btcruby/script.rb +733 -0
  76. data/lib/btcruby/signature_hashtype.rb +37 -0
  77. data/lib/btcruby/transaction.rb +511 -0
  78. data/lib/btcruby/transaction_builder/errors.rb +15 -0
  79. data/lib/btcruby/transaction_builder/provider.rb +54 -0
  80. data/lib/btcruby/transaction_builder/result.rb +73 -0
  81. data/lib/btcruby/transaction_builder/signer.rb +28 -0
  82. data/lib/btcruby/transaction_builder.rb +520 -0
  83. data/lib/btcruby/transaction_input.rb +298 -0
  84. data/lib/btcruby/transaction_outpoint.rb +30 -0
  85. data/lib/btcruby/transaction_output.rb +315 -0
  86. data/lib/btcruby/version.rb +3 -0
  87. data/lib/btcruby/wif.rb +118 -0
  88. data/lib/btcruby/wire_format.rb +362 -0
  89. data/lib/btcruby.rb +44 -2
  90. data/sample_code/creating_a_p2sh_multisig_address.rb +21 -0
  91. data/sample_code/creating_a_transaction_manually.rb +44 -0
  92. data/sample_code/generating_an_address.rb +20 -0
  93. data/sample_code/using_transaction_builder.rb +49 -0
  94. data/spec/address_spec.rb +206 -0
  95. data/spec/all.rb +6 -0
  96. data/spec/base58_spec.rb +83 -0
  97. data/spec/block_header_spec.rb +18 -0
  98. data/spec/block_spec.rb +18 -0
  99. data/spec/currency_formatter_spec.rb +46 -0
  100. data/spec/data_spec.rb +50 -0
  101. data/spec/diagnostics_spec.rb +41 -0
  102. data/spec/key_spec.rb +205 -0
  103. data/spec/keychain_spec.rb +261 -0
  104. data/spec/network_spec.rb +48 -0
  105. data/spec/open_assets/asset_address_spec.rb +33 -0
  106. data/spec/open_assets/asset_id_spec.rb +15 -0
  107. data/spec/open_assets/asset_marker_spec.rb +47 -0
  108. data/spec/open_assets/asset_processor_spec.rb +567 -0
  109. data/spec/open_assets/asset_transaction_builder_spec.rb +273 -0
  110. data/spec/open_assets/asset_transaction_spec.rb +70 -0
  111. data/spec/proof_of_work_spec.rb +53 -0
  112. data/spec/script_spec.rb +66 -0
  113. data/spec/spec_helper.rb +8 -0
  114. data/spec/transaction_builder_spec.rb +338 -0
  115. data/spec/transaction_spec.rb +162 -0
  116. data/spec/wire_format_spec.rb +283 -0
  117. metadata +141 -7
@@ -0,0 +1,418 @@
1
+ # Note: additional files are required in the bottom of the file.
2
+
3
+ # High-level Transaction Builder for OpenAssets protocol.
4
+ # https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
5
+ module BTC
6
+ # TODO:
7
+ # - provide Asset ID(s) as input, or raw AssetTransactionInput objects
8
+ # - provide raw unspents to pay mining fees
9
+ # - provide asset change address
10
+ # - provide btc change address
11
+ # - provide issuance API, transfer API, payment API (plain btc outputs)
12
+ # Use TransactionBuilder internally.
13
+ class AssetTransactionBuilder
14
+
15
+ # Network to validate provided addresses against.
16
+ # Default value is `Network.default`.
17
+ attr_accessor :network
18
+
19
+ # Must be a subclass of a BTC::BitcoinPaymentAddress
20
+ attr_accessor :bitcoin_change_address
21
+
22
+ # Must be a subclass of a BTC::AssetAddress
23
+ attr_accessor :asset_change_address
24
+
25
+ # Enumerable yielding BTC::TransactionOutput instances with valid `transaction_hash` and `index` properties.
26
+ # If not specified, `bitcoin_unspent_outputs_provider` is used if possible.
27
+ attr_accessor :bitcoin_unspent_outputs
28
+
29
+ # Enumerable yielding BTC::AssetTransactionOutput instances with valid `transaction_hash` and `index` properties.
30
+ attr_accessor :asset_unspent_outputs
31
+
32
+ # Provider of the pure bitcoin unspent outputs adopting TransactionBuilder::Provider.
33
+ # If not specified, `bitcoin_unspent_outputs` must be provided.
34
+ attr_accessor :bitcoin_provider
35
+
36
+ # Provider of the pure bitcoin unspent outputs adopting AssetTransactionBuilder::Provider.
37
+ # If not specified, `asset_unspent_outputs` must be provided.
38
+ attr_accessor :asset_provider
39
+
40
+ # TransactionBuilder::Signer for all the inputs (bitcoins and assets).
41
+ # If not provided, inputs will be left unsigned and `result.unsigned_input_indexes` will contain indexes of these inputs.
42
+ attr_accessor :signer
43
+
44
+ # Miner's fee per kilobyte (1000 bytes).
45
+ # Default is Transaction::DEFAULT_FEE_RATE
46
+ attr_accessor :fee_rate
47
+
48
+ # Metadata to embed in the marker output. Default is nil (empty string).
49
+ attr_accessor :metadata
50
+
51
+ def initialize
52
+ end
53
+
54
+ def asset_unspent_outputs=(unspents)
55
+ self.asset_provider = Provider.new{|atxbuilder| unspents }
56
+ end
57
+
58
+ # Adds an issuance of some assets.
59
+ # If `script` is specified, it is used to create an intermediate base transaction.
60
+ # If `output` is specified, it must be a valid spendable output with `transaction_id` and `index`.
61
+ # It can be regular TransactionOutput or verified AssetTransactionOutput.
62
+ # `amount` must be > 0 - number of units to be issued
63
+ def issue_asset(source_script: nil, source_output: nil,
64
+ amount: nil,
65
+ script: nil, address: nil)
66
+ raise ArgumentError, "Either `source_script` or `source_output` must be specified" if !source_script && !source_output
67
+ raise ArgumentError, "Both `source_script` and `source_output` cannot be specified" if source_script && source_output
68
+ raise ArgumentError, "Either `script` or `address` must be specified" if !script && !address
69
+ raise ArgumentError, "Both `script` and `address` cannot be specified" if script && address
70
+ raise ArgumentError, "Amount must be greater than zero" if !amount || amount <= 0
71
+ if source_output && (!source_output.index || !source_output.transaction_hash)
72
+ raise ArgumentError, "If `source_output` is specified, it must have valid `transaction_hash` and `index` attributes"
73
+ end
74
+ script ||= AssetAddress.parse(address).script
75
+
76
+ # Ensure source output is a verified asset output.
77
+ if source_output
78
+ if source_output.is_a?(AssetTransactionOutput)
79
+ raise ArgumentError, "Must be verified asset output to spend" if !source_output.verified?
80
+ else
81
+ source_output = AssetTransactionOutput.new(transaction_output: source_output, verified: true)
82
+ end
83
+ end
84
+
85
+ # Set either the script or output only once.
86
+ # All the remaining issuances must use the same script or output.
87
+ if !self.issuing_asset_script && !self.issuing_asset_output
88
+ self.issuing_asset_script = source_script
89
+ self.issuing_asset_output = source_output
90
+ else
91
+ if self.issuing_asset_script != source_script || self.issuing_asset_output != source_output
92
+ raise ArgumentError, "Can't issue more assets from a different source script or source output"
93
+ end
94
+ end
95
+ self.issued_assets << {amount: amount, script: script}
96
+ end
97
+
98
+ # Adds a transfer output.
99
+ # May override per-builder unspents/provider/change address to allow multi-user swaps.
100
+ def transfer_asset(asset_id: nil,
101
+ amount: nil,
102
+ script: nil, address: nil,
103
+ provider: nil, unspent_outputs: nil, change_address: nil)
104
+ raise ArgumentError, "AssetID must be provided" if !asset_id
105
+ raise ArgumentError, "Either `script` or `address` must be specified" if !script && !address
106
+ raise ArgumentError, "Both `script` and `address` cannot be specified" if script && address
107
+ raise ArgumentError, "Amount must be greater than zero" if !amount || amount <= 0
108
+
109
+ provider = Provider.new{|atxbuilder| unspent_outputs } if unspent_outputs
110
+ change_address = AssetAddress.parse(change_address) if change_address
111
+
112
+ asset_id = AssetID.parse(asset_id)
113
+ script ||= AssetAddress.parse(address).script
114
+
115
+ self.transferred_assets << {asset_id: asset_id, amount: amount, script: script, provider: provider, change_address: change_address}
116
+ end
117
+
118
+ # Adds a normal payment output. Typically used for transfer
119
+ def send_bitcoin(output: nil, amount: nil, script: nil, address: nil)
120
+ if !output
121
+ raise ArgumentError, "Either `script` or `address` must be specified" if !script && !address
122
+ raise ArgumentError, "Amount must be specified (>= 0)" if (!amount || amount < 0)
123
+ script ||= address.public_address.script if address
124
+ output = TransactionOutput.new(value: amount, script: script)
125
+ end
126
+ self.bitcoin_outputs << output
127
+ end
128
+
129
+ def bitcoin_change_address=(addr)
130
+ @bitcoin_change_address = BitcoinPaymentAddress.parse(addr)
131
+ end
132
+
133
+ def asset_change_address=(addr)
134
+ @asset_change_address = AssetAddress.parse(addr)
135
+ end
136
+
137
+ def build
138
+
139
+ validate_bitcoin_change_address!
140
+ validate_asset_change_address!
141
+
142
+ result = Result.new
143
+
144
+ # We don't count assets_cost because outputs of these txs
145
+ # will be consumed in the next transaction. Only add the fees.
146
+ if self.issuing_asset_script
147
+ issuing_tx, unsigned_input_indexes = make_transaction_for_issues
148
+ result.fee = issuing_tx.fee
149
+ result.transactions << issuing_tx
150
+ result.unsigned_input_indexes << unsigned_input_indexes
151
+ self.issuing_asset_output = AssetTransactionOutput.new(transaction_output: issuing_tx.outputs.first, verified: true)
152
+ end
153
+
154
+ # Prepare the target transaction
155
+ txbuilder = make_transaction_builder
156
+ if result.transactions.size > 0
157
+ txbuilder.parent_transactions = [result.transactions.last]
158
+ end
159
+ txbuilder.prepended_unspent_outputs ||= []
160
+ txbuilder.outputs = []
161
+
162
+ # Add issuance input and outputs first.
163
+
164
+ consumed_asset_outputs = [] # used in inputs
165
+ issue_outputs = []
166
+ transfer_outputs = []
167
+
168
+ if self.issued_assets.size > 0
169
+ txbuilder.prepended_unspent_outputs << self.issuing_asset_output.transaction_output
170
+ self.issued_assets.each do |issue|
171
+ atxo = make_asset_transaction_output(asset_id: issuing_asset_id, amount: issue[:amount], script: issue[:script])
172
+ issue_outputs << atxo
173
+ end
174
+ end
175
+
176
+ # Move all transfers of the asset ID used on the issuing output to the top of the list.
177
+ asset_id_on_the_issuing_input = self.issuing_asset_output ? self.issuing_asset_output.asset_id : nil
178
+ if asset_id_on_the_issuing_input
179
+ aid = asset_id_on_the_issuing_input.to_s
180
+ self.transferred_assets = self.transferred_assets.sort do |a,b|
181
+ # move the asset id used in the issue input to the top
182
+ if a[:asset_id].to_s == aid
183
+ -1
184
+ elsif b[:asset_id] == aid
185
+ 1
186
+ else
187
+ 0 # keep the order
188
+ end
189
+ end
190
+ end
191
+
192
+ consumed_outpoints = {} # "txid:index" => true
193
+
194
+ self.transferred_assets.each do |transfer| # |aid,transfers|
195
+ asset_id = transfer[:asset_id]
196
+ transfers = [transfer]
197
+ amount_required = 0
198
+ amount_provided = 0
199
+ if asset_id_on_the_issuing_input.to_s == asset_id.to_s
200
+ amount_provided = self.issuing_asset_output.value
201
+ end
202
+
203
+ atxo = make_asset_transaction_output(asset_id: transfer[:asset_id],
204
+ amount: transfer[:amount],
205
+ script: transfer[:script])
206
+ amount_required += atxo.value
207
+ transfer_outputs << atxo
208
+
209
+ # Fill in enough unspent assets for this asset_id.
210
+ # Use per-transfer provider if it's specified. Otherwise use global provider.
211
+ provider = transfer[:provider] || self.asset_provider
212
+ unspents = provider.asset_unspent_outputs(asset_id: asset_id, amount: amount_required).dup
213
+
214
+ while amount_provided < amount_required
215
+ autxo = unspents.shift
216
+ if !autxo
217
+ raise InsufficientFundsError, "Not enough outputs for asset #{asset_id.to_s} (#{amount_provided} available < #{amount_required} required)"
218
+ end
219
+ if !consumed_outpoints[oid = autxo.transaction_output.outpoint_id]
220
+ # Only apply outputs with matching asset ids.
221
+ if autxo.asset_id == asset_id
222
+ raise ArgumentError, "Must be verified asset outputs to spend" if !autxo.verified?
223
+ consumed_outpoints[oid] = true
224
+ amount_provided += autxo.value
225
+ consumed_asset_outputs << autxo
226
+ txbuilder.prepended_unspent_outputs << autxo.transaction_output
227
+ end
228
+ end
229
+ end
230
+
231
+ # If the difference is > 0, add a change output
232
+ change = amount_provided - amount_required
233
+ if change > 0
234
+ # Use per-transfer change address if it's specified. Otherwise use global change address.
235
+ change_addr = transfer[:change_address] || self.asset_change_address
236
+ atxo = make_asset_transaction_output(asset_id: asset_id,
237
+ amount: change,
238
+ script: change_addr.script)
239
+ transfer_outputs << atxo
240
+ end
241
+
242
+ end # each transfer
243
+
244
+ # If we have an asset on the issuance input and it is never used in any transfer,
245
+ # then we need to create a change output just for it.
246
+ if asset_id_on_the_issuing_input && self.issuing_asset_output.value > 0
247
+ if !self.transferred_assets.map{|dict| dict[:asset_id].to_s }.uniq.include?(asset_id_on_the_issuing_input.to_s)
248
+ atxo = make_asset_transaction_output(asset_id: self.issuing_asset_output.asset_id,
249
+ amount: self.issuing_asset_output.value,
250
+ script: self.asset_change_address.script)
251
+ transfer_outputs << atxo
252
+ end
253
+ end
254
+
255
+ all_asset_outputs = (issue_outputs + transfer_outputs)
256
+ result.assets_cost = all_asset_outputs.inject(0){|sum, atxo| sum + atxo.transaction_output.value }
257
+ marker = AssetMarker.new(quantities: all_asset_outputs.map{|atxo| atxo.value }, metadata: self.metadata)
258
+
259
+ # Now, add underlying issues, marker and transfer outputs
260
+ issue_outputs.each do |atxo|
261
+ txbuilder.outputs << atxo.transaction_output
262
+ end
263
+ txbuilder.outputs << marker.output
264
+ transfer_outputs.each do |atxo|
265
+ txbuilder.outputs << atxo.transaction_output
266
+ end
267
+
268
+ txresult = txbuilder.build
269
+ tx = txresult.transaction
270
+ atx = AssetTransaction.new(transaction: tx)
271
+
272
+ BTC::Invariant(atx.outputs.size == all_asset_outputs.size + 1 + (txresult.change_amount > 0 ? 1 : 0),
273
+ "Must have all asset outputs (with marker output and optional change output)");
274
+
275
+ if txresult.change_amount > 0
276
+ plain_change_output = atx.outputs.last
277
+ BTC::Invariant(!plain_change_output.verified?, "Must have plain change output not verified");
278
+ BTC::Invariant(!plain_change_output.asset_id, "Must have plain change output not have asset id");
279
+ BTC::Invariant(!plain_change_output.value, "Must have plain change output not have asset amount");
280
+ plain_change_output.verified = true # to match the rest of outputs.
281
+ end
282
+
283
+ # Provide color info for each input
284
+ consumed_asset_outputs.each_with_index do |atxo, i|
285
+ atx.inputs[i].asset_id = atxo.asset_id
286
+ atx.inputs[i].value = atxo.value
287
+ atx.inputs[i].verified = true
288
+ end
289
+ atx.inputs[consumed_asset_outputs.size..-1].each do |input|
290
+ input.asset_id = nil
291
+ input.value = nil
292
+ input.verified = true
293
+ end
294
+
295
+ # Provide color info for each output
296
+ issue_outputs.each_with_index do |aout1, i|
297
+ aout = atx.outputs[i]
298
+ aout.asset_id = aout1.asset_id
299
+ aout.value = aout1.value
300
+ aout.verified = true
301
+ end
302
+ atx.outputs[issue_outputs.size].verified = true # make marker verified
303
+ offset = 1 + issue_outputs.size # +1 for marker
304
+ transfer_outputs.each_with_index do |aout1, i|
305
+ aout = atx.outputs[i + offset]
306
+ aout.asset_id = aout1.asset_id
307
+ aout.value = aout1.value
308
+ aout.verified = true
309
+ end
310
+ offset = 1 + issue_outputs.size + transfer_outputs.size # +1 for marker
311
+ atx.outputs[offset..-1].each do |aout|
312
+ aout.asset_id = nil
313
+ aout.value = nil
314
+ aout.verified = true
315
+ end
316
+
317
+ if txresult.change_amount == 0
318
+ atx.outputs.each do |aout|
319
+ if !aout.marker?
320
+ BTC::Invariant(aout.verified?, "Must be verified");
321
+ BTC::Invariant(!!aout.asset_id, "Must have asset id");
322
+ BTC::Invariant(aout.value && aout.value > 0, "Must have some asset amount");
323
+ end
324
+ end
325
+ end
326
+
327
+ result.unsigned_input_indexes << txresult.unsigned_input_indexes
328
+ result.transactions << tx
329
+ result.asset_transaction = atx
330
+
331
+ result
332
+ end
333
+
334
+ # Helpers
335
+
336
+ def issuing_asset_id
337
+ @issuing_asset_id ||= AssetID.new(script: @issuing_asset_script || @issuing_asset_output.transaction_output.script)
338
+ end
339
+
340
+ def make_asset_transaction_output(asset_id: nil, amount: nil, script: nil)
341
+ txout = make_output_for_asset_script(script)
342
+ AssetTransactionOutput.new(transaction_output: txout, asset_id: asset_id, value: amount, verified: true)
343
+ end
344
+
345
+ def make_transaction_for_issues
346
+ raise "Sanity check" if !self.issuing_asset_script
347
+ txbuilder = make_transaction_builder
348
+ txbuilder.outputs = [
349
+ make_output_for_asset_script(self.issuing_asset_script)
350
+ ]
351
+ result = txbuilder.build
352
+ [result.transaction, result.unsigned_input_indexes]
353
+ end
354
+
355
+ def make_output_for_asset_script(script)
356
+ txout = BTC::TransactionOutput.new(value: MAX_MONEY, script: script)
357
+ txout.value = txout.dust_limit
358
+ raise RuntimeError, "Sanity check: txout value must not be zero" if txout.value <= 0
359
+ txout
360
+ end
361
+
362
+ def make_transaction_builder
363
+ txbuilder = TransactionBuilder.new
364
+ txbuilder.change_address = self.bitcoin_change_address
365
+ txbuilder.signer = self.signer
366
+ txbuilder.provider = self.internal_bitcoin_provider
367
+ txbuilder.unspent_outputs = self.bitcoin_unspent_outputs
368
+ txbuilder.fee_rate = self.fee_rate
369
+ txbuilder
370
+ end
371
+
372
+ def internal_bitcoin_provider
373
+ @internal_bitcoin_provider ||= (self.bitcoin_provider || TransactionBuilder::Provider.new{|txb| []})
374
+ end
375
+
376
+
377
+ # Validation Methods
378
+
379
+ def validate_bitcoin_change_address!
380
+ addr = self.bitcoin_change_address
381
+ raise ArgumentError, "Missing bitcoin_change_address" if !addr
382
+ raise ArgumentError, "bitcoin_change_address must be an instance of BTC::Address" if !addr.is_a?(Address)
383
+ raise ArgumentError, "bitcoin_change_address must not be an instance of BTC::AssetAddress" if addr.is_a?(AssetAddress)
384
+ end
385
+
386
+ def validate_asset_change_address!
387
+ self.transferred_assets.each do |dict|
388
+ addr = dict[:change_address] || self.asset_change_address
389
+ raise ArgumentError, "Missing asset_change_address" if !addr
390
+ raise ArgumentError, "asset_change_address must be an instance of BTC::AssetAddress" if !addr.is_a?(AssetAddress)
391
+ end
392
+ end
393
+
394
+ protected
395
+ attr_accessor :issuing_asset_script
396
+ attr_accessor :issuing_asset_output
397
+ attr_accessor :issued_assets
398
+ attr_accessor :transferred_assets
399
+ attr_accessor :bitcoin_outputs
400
+
401
+ def issued_assets
402
+ @issued_assets ||= []
403
+ end
404
+
405
+ def transferred_assets
406
+ @transferred_assets ||= []
407
+ end
408
+
409
+ def bitcoin_outputs
410
+ @bitcoin_outputs ||= []
411
+ end
412
+
413
+ end
414
+ end
415
+
416
+ require_relative 'asset_transaction_builder/errors.rb'
417
+ require_relative 'asset_transaction_builder/result.rb'
418
+ require_relative 'asset_transaction_builder/provider.rb'
@@ -0,0 +1,64 @@
1
+ # Implementation of OpenAssets protocol.
2
+ # https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
3
+ module BTC
4
+ class AssetTransactionInput
5
+ attr_accessor :index
6
+ attr_accessor :transaction_input # BTC::TransactionInput
7
+
8
+ # Verified inputs with `asset_id == nil` are *uncolored*.
9
+ # Non-verified inputs are not known to have assets associated with them.
10
+ attr_accessor :asset_id
11
+ attr_accessor :value
12
+ attr_accessor :verified # true if asset_id and value are verified and can be used.
13
+
14
+ def initialize(transaction_input: nil, asset_id: nil, value: nil, verified: false)
15
+ raise ArgumentError, "No transaction_input provided" if !transaction_input
16
+ @transaction_input = transaction_input
17
+ @index = transaction_input ? transaction_input.index : nil
18
+ @asset_id = asset_id
19
+ @value = value
20
+ @verified = !!verified
21
+ end
22
+
23
+ # Input is verified when we know for sure if it's colored or not and if it is,
24
+ # what asset and how much is associated with it.
25
+ def verified?
26
+ !!@verified
27
+ end
28
+
29
+ # Input is colored when it has AssetID and a positive value.
30
+ def colored?
31
+ !!@asset_id && @value && @value > 0
32
+ end
33
+
34
+ def index
35
+ @index ||= @transaction_input.index
36
+ end
37
+
38
+ def assets_data
39
+ data = "".b
40
+ data << WireFormat.encode_uint8(@verified ? 1 : 0)
41
+ data << WireFormat.encode_string(@asset_id ? @asset_id.hash : "".b)
42
+ data << WireFormat.encode_int64le(@value.to_i)
43
+ data
44
+ end
45
+
46
+ # Returns total length read
47
+ def parse_assets_data(data, offset: 0)
48
+ v, len = WireFormat.read_uint8(data: data, offset: offset)
49
+ raise ArgumentError, "Invalid data: verified flag" if !v
50
+ asset_hash, len = WireFormat.read_string(data: data, offset: len) # empty or 20 bytes
51
+ raise ArgumentError, "Invalid data: asset hash" if !asset_hash
52
+ value, len = WireFormat.read_int64le(data: data, offset: len)
53
+ raise ArgumentError, "Invalid data: value" if !value
54
+ @verified = (v == 1)
55
+ @asset_id = asset_hash.bytesize > 0 ? AssetID.new(hash: asset_hash) : nil
56
+ @value = value
57
+ len
58
+ end
59
+
60
+ def ==(other)
61
+ super(other) || self.transaction_input == other.transaction_input
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,140 @@
1
+ # Implementation of OpenAssets protocol.
2
+ # https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki
3
+ module BTC
4
+ class AssetTransactionOutput
5
+ KIND_ISSUE = :issue
6
+ KIND_TRANSFER = :transfer
7
+ KIND_MARKER = :marker
8
+
9
+ attr_reader :index
10
+ attr_accessor :transaction_output # BTC::TransactionOutput
11
+
12
+ attr_accessor :kind
13
+ attr_accessor :issue # if true, then it's an issue output
14
+ attr_accessor :transfer # if true, then it's a transfer output
15
+ attr_accessor :marker # if true, then it's a marker output
16
+
17
+ # Verified outputs with `asset_id == nil` are *uncolored*.
18
+ # Non-verified outputs are not known to have assets associated with them.
19
+ attr_accessor :asset_id
20
+ attr_accessor :value
21
+ attr_accessor :verified # true if asset_id and value are verified and can be used.
22
+
23
+ def initialize(transaction_output: nil, asset_id: nil, value: nil, verified: false, issue: nil, transfer: nil, marker: nil)
24
+ raise ArgumentError, "No transaction_output provided" if !transaction_output
25
+ @transaction_output = transaction_output
26
+ @index = transaction_output ? transaction_output.index : nil
27
+ @asset_id = asset_id
28
+ @value = value
29
+ @verified = !!verified
30
+ self.issue = issue if issue
31
+ self.transfer = transfer if transfer
32
+ self.marker = marker if marker
33
+ end
34
+
35
+ # Output is verified when we know for sure if it's colored or not and if it is,
36
+ # what asset and how much is associated with it.
37
+ def verified?
38
+ !!@verified
39
+ end
40
+
41
+ # Output is colored when it has AssetID and a positive value.
42
+ def colored?
43
+ !!@asset_id && has_value?
44
+ end
45
+
46
+ def has_value?
47
+ !!@value && @value > 0
48
+ end
49
+
50
+ def issue=(issue)
51
+ raise ArgumentError, "Can only set `issue` to true" if !issue
52
+ self.kind = KIND_ISSUE
53
+ issue
54
+ end
55
+
56
+ def transfer=(transfer)
57
+ raise ArgumentError, "Can only set `transfer` to true" if !transfer
58
+ self.kind = KIND_TRANSFER
59
+ transfer
60
+ end
61
+
62
+ def marker=(marker)
63
+ raise ArgumentError, "Can only set `marker` to true" if !marker
64
+ self.kind = KIND_MARKER
65
+ marker
66
+ end
67
+
68
+ def kind=(kind)
69
+ @kind = kind
70
+ @marker = nil
71
+ end
72
+
73
+ # Returns `true` if this output may issue new amount of some asset.
74
+ def issue
75
+ @kind == KIND_ISSUE
76
+ end
77
+ alias :issue? :issue
78
+
79
+ # Returns `true` if this output may transfer new amount of some asset.
80
+ def transfer
81
+ @kind == KIND_TRANSFER
82
+ end
83
+ alias :transfer? :transfer
84
+
85
+ # Returns `true` if this output corresponds to a marker output (always uncolored)
86
+ def marker
87
+ @marker ||= (@kind == KIND_MARKER ? AssetMarker.new(output: @transaction_output) : nil)
88
+ end
89
+
90
+ def marker?
91
+ !!marker
92
+ end
93
+
94
+ def index
95
+ @index ||= @transaction_output.index
96
+ end
97
+
98
+ def transaction_hash
99
+ @transaction_output.transaction_hash
100
+ end
101
+
102
+ def transaction_id
103
+ @transaction_output.transaction_id
104
+ end
105
+
106
+ def outpoint
107
+ @transaction_output.outpoint
108
+ end
109
+
110
+ def outpoint_id
111
+ @transaction_output.outpoint_id
112
+ end
113
+
114
+ def assets_data
115
+ data = "".b
116
+ data << WireFormat.encode_uint8(@verified ? 1 : 0)
117
+ data << WireFormat.encode_string(@asset_id ? @asset_id.hash : "".b)
118
+ data << WireFormat.encode_int64le(@value.to_i)
119
+ data
120
+ end
121
+
122
+ # Returns total length read
123
+ def parse_assets_data(data, offset: 0)
124
+ v, len = WireFormat.read_uint8(data: data, offset: offset)
125
+ raise ArgumentError, "Invalid data: verified flag" if !v
126
+ asset_hash, len = WireFormat.read_string(data: data, offset: len) # empty or 20 bytes
127
+ raise ArgumentError, "Invalid data: asset hash" if !asset_hash
128
+ value, len = WireFormat.read_int64le(data: data, offset: len)
129
+ raise ArgumentError, "Invalid data: value" if !value
130
+ @verified = (v == 1)
131
+ @asset_id = asset_hash.bytesize > 0 ? AssetID.new(hash: asset_hash) : nil
132
+ @value = value
133
+ len
134
+ end
135
+
136
+ def ==(other)
137
+ super(other) || self.transaction_output == other.transaction_output
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,26 @@
1
+ # Implementation of [OpenAssets protocol](https://github.com/OpenAssets/open-assets-protocol/blob/master/specification.mediawiki).
2
+ # * AssetID uniquely identifies an asset and conditions of issuance.
3
+ # * AssetAddress is an address format that allows sending and receiving assets.
4
+ # * Asset represents an asset source (includes AssetID and output).
5
+ # * AssetMarker represents a marker specifying asset issuance and transfer.
6
+ #
7
+ # Tasks:
8
+ # * Issuing a new asset.
9
+ # * Issuing more units of a given asset. If the issuing address holds some assets, they must be re-created.
10
+ # * Tracking asset origin via proofchain of transactions with block merkle paths.
11
+ # * Transferring assets.
12
+ # * Transferring assets with payment in the same transaction.
13
+ # * Non-interactive decentralized order book.
14
+ # * Supporting issue-once assets (anchored to a genesis transaction ID).
15
+ # * Supporting stock split (AssetV2 is issued by consuming AssetV1, validating software must validate AssetV2 accordingly).
16
+ # * Supporting key rotation (can be done using the same technique as with stock split). Maybe use metadata to link to a previous asset.
17
+ require_relative 'open_assets/asset_id.rb'
18
+ require_relative 'open_assets/asset_address.rb'
19
+ require_relative 'open_assets/asset.rb'
20
+ require_relative 'open_assets/asset_marker.rb'
21
+ require_relative 'open_assets/asset_transaction.rb'
22
+ require_relative 'open_assets/asset_transaction_input.rb'
23
+ require_relative 'open_assets/asset_transaction_output.rb'
24
+ require_relative 'open_assets/asset_processor.rb'
25
+ require_relative 'open_assets/asset_transaction_builder.rb'
26
+ require_relative 'open_assets/asset_definition.rb'