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,338 @@
1
+ require_relative 'spec_helper'
2
+ describe BTC::TransactionBuilder do
3
+
4
+ class SignerByKey
5
+ include TransactionBuilder::Signer
6
+ def initialize(&block)
7
+ @block = block
8
+ end
9
+ def signing_key_for_output(output: nil, address: nil)
10
+ @block.call(output, address)
11
+ end
12
+ end
13
+
14
+ class SignerBySignatureScript
15
+ include TransactionBuilder::Signer
16
+ def initialize(&block)
17
+ @block = block
18
+ end
19
+ def signature_script_for_input(input: nil, output: nil)
20
+ @block.call(input, output)
21
+ end
22
+ end
23
+
24
+ describe "TransactionBuilder with no outputs" do
25
+ before do
26
+ @builder = TransactionBuilder.new
27
+ @all_utxos = self.mock_utxos
28
+
29
+ @builder.input_addresses = self.mock_addresses
30
+ @builder.provider = TransactionBuilder::Provider.new do |txb|
31
+ addrs = txb.public_addresses
32
+ addrs.must_equal self.mock_addresses
33
+ scripts = addrs.map{|a| a.script }.uniq
34
+ @all_utxos.find_all{|utxo| scripts.include?(utxo.script) }
35
+ end
36
+ @builder.change_address = Address.parse("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
37
+ end
38
+
39
+ it "should fill unspent_outputs using Provider" do
40
+ @builder.unspent_outputs.must_equal @all_utxos
41
+ end
42
+
43
+ it "should have a default fee rate" do
44
+ @builder.fee_rate.must_equal Transaction::DEFAULT_FEE_RATE
45
+ end
46
+
47
+ it "should return a result" do
48
+ result = @builder.build
49
+ result.class.must_equal TransactionBuilder::Result
50
+ end
51
+
52
+ it "should compose a fully-spending transaction when no outputs are given" do
53
+ result = @builder.build
54
+ tx = result.transaction
55
+ tx.inputs.size.must_equal mock_utxos.size
56
+ tx.outputs.size.must_equal 1 # only one change address
57
+ end
58
+
59
+ it "should have a list of unsigned indexes" do
60
+ result = @builder.build
61
+ result.unsigned_input_indexes.must_equal (0...(mock_utxos.size)).to_a
62
+ end
63
+
64
+ it "should have a reasonable fee" do
65
+ result = @builder.build
66
+ size = result.transaction.data.bytesize # size of unsigned transaction
67
+ result.fee.must_be :>=, (size / 1000)*Transaction::DEFAULT_FEE_RATE
68
+ result.fee.must_be :<, 0.01 * BTC::COIN
69
+ end
70
+
71
+ it "should have valid amounts" do
72
+ result = @builder.build
73
+ result.inputs_amount.must_equal mock_utxos.inject(0){|sum, out| sum + out.value}
74
+ result.outputs_amount.must_be :<=, result.inputs_amount
75
+ # Balance should be correct
76
+ result.inputs_amount.must_equal (result.outputs_amount + result.fee)
77
+ result.transaction.inputs.each do |txin|
78
+ # Unsigned txins contain respective output scripts.
79
+ # Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
80
+ txin.signature_script.data_only?.must_equal false
81
+ txin.signature_script.p2pkh?.must_equal true
82
+ end
83
+ end
84
+
85
+ it "should sign if WIFs are provided" do
86
+ @builder.input_addresses = mock_wifs
87
+ result = @builder.build
88
+ result.unsigned_input_indexes.must_equal []
89
+ result.transaction.inputs.each do |txin|
90
+ # Unsigned txins contain respective output scripts.
91
+ # Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
92
+ txin.signature_script.data_only?.must_equal true
93
+ txin.signature_script.p2pkh?.must_equal false
94
+ end
95
+ end
96
+
97
+ it "should sign if signer provides a key" do
98
+ @builder.signer = SignerByKey.new do |txout, addr|
99
+ wif = mock_wifs.find{|wif| wif.public_address == addr}
100
+ wif.key
101
+ end
102
+ result = @builder.build
103
+ result.unsigned_input_indexes.must_equal []
104
+ result.transaction.inputs.each do |txin|
105
+ # Unsigned txins contain respective output scripts.
106
+ # Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
107
+ txin.signature_script.data_only?.must_equal true
108
+ txin.signature_script.p2pkh?.must_equal false
109
+ end
110
+ end
111
+
112
+ it "should sign if signer provides a signature script" do
113
+ @builder.signer = SignerBySignatureScript.new do |txin, txout|
114
+ Script.new << "signature" << "pubkey"
115
+ end
116
+ result = @builder.build
117
+ result.unsigned_input_indexes.must_equal []
118
+ result.transaction.inputs.each do |txin|
119
+ # Unsigned txins contain respective output scripts.
120
+ # Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
121
+ txin.signature_script.data_only?.must_equal true
122
+ txin.signature_script.p2pkh?.must_equal false
123
+ end
124
+ end
125
+
126
+ it "should include all prepended unspents AND all normal unspents" do
127
+ @builder.prepended_unspent_outputs = [ BTC::TransactionOutput.new(
128
+ value: 6_666_666,
129
+ script: Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX").script,
130
+ index: 0,
131
+ transaction_hash: "some mock tx".hash256
132
+ ) ]
133
+ result = @builder.build
134
+ result.inputs_amount.must_equal mock_utxos.inject(0){|sum, out| sum + out.value} + 6_666_666
135
+ result.transaction.inputs.size.must_equal mock_utxos.size + 1
136
+ end
137
+ end
138
+
139
+
140
+
141
+
142
+ describe "TransactionBuilder with some outputs" do
143
+ before do
144
+ @builder = TransactionBuilder.new
145
+ @all_utxos = self.mock_utxos
146
+ @builder.input_addresses = self.mock_addresses
147
+ @builder.provider = TransactionBuilder::Provider.new do |txb|
148
+ addrs = txb.public_addresses
149
+ addrs.must_equal self.mock_addresses
150
+ scripts = addrs.map{|a| a.script }.uniq
151
+ @all_utxos.find_all{|utxo| scripts.include?(utxo.script) }
152
+ end
153
+ @builder.change_address = Address.parse("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
154
+ @builder.outputs = [ TransactionOutput.new(value: 1000_500,
155
+ script: Address.parse("1TipsuQ7CSqfQsjA9KU5jarSB1AnrVLLo").script) ]
156
+ end
157
+
158
+ it "should fill unspent_outputs using provider" do
159
+ @builder.unspent_outputs.must_equal @all_utxos
160
+ end
161
+
162
+ it "should have a default fee rate" do
163
+ @builder.fee_rate.must_equal Transaction::DEFAULT_FEE_RATE
164
+ end
165
+
166
+ it "should compose a minimal transaction to pay necessary amount" do
167
+ result = @builder.build
168
+ tx = result.transaction
169
+ tx.inputs.size.must_equal 11
170
+ tx.outputs.size.must_equal 2 # one change address and one output address
171
+
172
+ # Payment address
173
+ tx.outputs.first.value.must_equal 1000_500
174
+ tx.outputs.first.script.standard_address.to_s.must_equal "1TipsuQ7CSqfQsjA9KU5jarSB1AnrVLLo"
175
+
176
+ # Change address
177
+ tx.outputs.last.value.must_equal (1000_00*11 - 1000_500 - result.fee)
178
+ tx.outputs.last.script.standard_address.to_s.must_equal "1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG"
179
+ end
180
+
181
+ it "should have a list of unsigned indexes" do
182
+ result = @builder.build
183
+ result.unsigned_input_indexes.must_equal (0...(result.transaction.inputs.size)).to_a
184
+ end
185
+
186
+ it "should have a reasonable fee" do
187
+ result = @builder.build
188
+ size = result.transaction.data.bytesize # size of unsigned transaction
189
+ result.fee.must_be :>=, (size / 1000)*Transaction::DEFAULT_FEE_RATE
190
+ result.fee.must_be :<, 0.01 * BTC::COIN
191
+ end
192
+
193
+ it "should have valid amounts" do
194
+ result = @builder.build
195
+ result.inputs_amount.must_equal 11*1000_00
196
+ result.outputs_amount.must_be :<=, result.inputs_amount
197
+ # Balance should be correct
198
+ result.inputs_amount.must_equal (result.outputs_amount + result.fee)
199
+ result.transaction.inputs.each do |txin|
200
+ # Unsigned txins contain respective output scripts.
201
+ # Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
202
+ txin.signature_script.data_only?.must_equal false
203
+ txin.signature_script.p2pkh?.must_equal true
204
+ end
205
+ end
206
+
207
+ it "should sign if WIFs are provided" do
208
+ @builder.input_addresses = mock_wifs
209
+ result = @builder.build
210
+ result.unsigned_input_indexes.must_equal []
211
+ result.transaction.inputs.each do |txin|
212
+ # Unsigned txins contain respective output scripts.
213
+ # Signed txins contain data-only signature script (for P2PKH it is signature + pubkey)
214
+ txin.signature_script.data_only?.must_equal true
215
+ txin.signature_script.p2pkh?.must_equal false
216
+ end
217
+ end
218
+
219
+ it "should include all prepended unspents and none of normal unspents if amount is covered" do
220
+ @builder.prepended_unspent_outputs = [ BTC::TransactionOutput.new(
221
+ value: 6_666_666,
222
+ script: Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX").script,
223
+ index: 0,
224
+ transaction_hash: "some mock tx".hash256
225
+ ) ]
226
+ result = @builder.build
227
+ result.inputs_amount.must_equal 6_666_666
228
+ result.transaction.inputs.size.must_equal 1
229
+ end
230
+
231
+ it "should include all prepended unspents and just enough of normal unspents" do
232
+ @builder.prepended_unspent_outputs = [ BTC::TransactionOutput.new(
233
+ value: @builder.outputs.first.value - (self.mock_utxos.first.value / 2),
234
+ script: Address.parse("3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX").script,
235
+ index: 0,
236
+ transaction_hash: "some mock tx".hash256
237
+ ) ]
238
+ result = @builder.build
239
+ result.inputs_amount.must_equal(@builder.outputs.first.value + (self.mock_utxos.first.value / 2))
240
+ result.transaction.inputs.size.must_equal 2
241
+ end
242
+ end
243
+
244
+ describe "TransactionBuilder edge cases" do
245
+ before do
246
+ @builder = TransactionBuilder.new
247
+ @all_utxos = self.mock_utxos
248
+ @builder.input_addresses = self.mock_addresses
249
+ @builder.provider = TransactionBuilder::Provider.new do |txb|
250
+ addrs = txb.public_addresses
251
+ addrs.must_equal self.mock_addresses
252
+ scripts = addrs.map{|a| a.script }.uniq
253
+ @all_utxos.find_all{|utxo| scripts.include?(utxo.script) }
254
+ end
255
+ @builder.change_address = Address.parse("1CBtcGivXmHQ8ZqdPgeMfcpQNJrqTrSAcG")
256
+ end
257
+
258
+ it "should detect missing change address" do
259
+ @builder.change_address = nil
260
+ lambda do
261
+ result = @builder.build
262
+ end.must_raise TransactionBuilder::MissingChangeAddressError
263
+ end
264
+
265
+ it "should detect missing unspents" do
266
+ @builder.provider = nil
267
+ lambda do
268
+ result = @builder.build
269
+ end.must_raise TransactionBuilder::MissingUnspentOutputsError
270
+ end
271
+
272
+ it "should detect missing unspents" do
273
+ @builder.unspent_outputs = [ ]
274
+ lambda do
275
+ result = @builder.build
276
+ end.must_raise TransactionBuilder::MissingUnspentOutputsError
277
+ end
278
+
279
+ it "should detect not enough unspents" do
280
+ @builder.outputs = [ TransactionOutput.new(value: 100*COIN, script: @builder.change_address.script) ]
281
+ lambda do
282
+ result = @builder.build
283
+ end.must_raise TransactionBuilder::InsufficientFundsError
284
+ end
285
+
286
+ it "should detect not enough unspents because of change constraints" do
287
+ @builder.dust_change = 0 # no coins are allowed to be lost
288
+ @builder.minimum_change = 10000
289
+ @builder.unspent_outputs = mock_utxos[0, 1]
290
+ @builder.outputs = [ TransactionOutput.new(value: 1000_00 - @builder.fee_rate - 10, script: @builder.change_address.script) ]
291
+ lambda do
292
+ result = @builder.build
293
+ end.must_raise TransactionBuilder::InsufficientFundsError
294
+ end
295
+
296
+ it "should forgo change if it's below dust level" do
297
+ @builder.dust_change = 42
298
+ @builder.minimum_change = 1000
299
+ @builder.unspent_outputs = mock_utxos[0, 1]
300
+ assumed_fee = 2590 #@builder.fee_rate # we assume we'll have sub-1K transaction
301
+ amount = 1000_00 - assumed_fee - @builder.dust_change
302
+ @builder.outputs = [ TransactionOutput.new(value: amount, script: @builder.change_address.script) ]
303
+
304
+ result = @builder.build
305
+ result.fee.must_equal assumed_fee
306
+ result.transaction.wont_equal nil
307
+ result.transaction.outputs.size.must_equal 1
308
+ result.transaction.outputs.first.value.must_equal amount
309
+ end
310
+
311
+ end
312
+
313
+ def mock_keys
314
+ @mock_keys ||= [
315
+ Key.new(private_key: "Wallet1".sha256),
316
+ Key.new(private_key: "Wallet2".sha256)
317
+ ]
318
+ end
319
+
320
+ def mock_addresses
321
+ mock_keys.map{|k| k.address }
322
+ end
323
+
324
+ def mock_wifs
325
+ mock_keys.map{|k| k.to_wif_object }
326
+ end
327
+
328
+ def mock_utxos
329
+ scripts = mock_addresses.map{|a| a.script }
330
+ (0...32).map do |i|
331
+ TransactionOutput.new(value: 100_000,
332
+ script: scripts[i % scripts.size],
333
+ transaction_hash: ((16+i).to_s(16)*32).from_hex,
334
+ index: i)
335
+ end
336
+ end
337
+
338
+ end
@@ -0,0 +1,162 @@
1
+ require_relative 'spec_helper'
2
+ describe BTC::Transaction do
3
+
4
+ it "should have core attributes" do
5
+
6
+ tx = BTC::Transaction.new
7
+
8
+ tx.data.to_hex.must_equal("01000000" + "0000" + "00000000")
9
+
10
+ tx.to_h.must_equal({
11
+ "hash"=>"d21633ba23f70118185227be58a63527675641ad37967e2aa461559f577aec43",
12
+ "ver"=>1,
13
+ "vin_sz"=>0,
14
+ "vout_sz"=>0,
15
+ "lock_time"=>0,
16
+ "size"=>10,
17
+ "in"=>[],
18
+ "out"=>[]
19
+ })
20
+
21
+ end
22
+
23
+ describe "Hash <-> ID conversion" do
24
+ before do
25
+ @txid = "43ec7a579f5561a42a7e9637ad4156672735a658be2752181801f723ba3316d2"
26
+ @txhash = @txid.from_hex.reverse
27
+ end
28
+
29
+ it "should convert tx ID to binary hash" do
30
+ BTC.hash_from_id(nil).must_equal nil
31
+ BTC.hash_from_id(@txid).must_equal @txhash
32
+ end
33
+
34
+ it "should convert binary hash to tx ID" do
35
+ BTC.id_from_hash(nil).must_equal nil
36
+ BTC.id_from_hash(@txhash).must_equal @txid
37
+ end
38
+
39
+ it "should convert hash to/from id for TransactionOutput" do
40
+ txout = TransactionOutput.new
41
+ txout.transaction_hash = @txhash
42
+ txout.transaction_id.must_equal @txid
43
+ txout.transaction_id = "deadbeef"
44
+ txout.transaction_hash.to_hex.must_equal "efbeadde"
45
+ end
46
+ end
47
+
48
+
49
+ describe "Amounts calculation" do
50
+ before do
51
+ @tx = Transaction.new
52
+ @tx.add_input(TransactionInput.new)
53
+ @tx.add_input(TransactionInput.new)
54
+ @tx.add_output(TransactionOutput.new(value: 123))
55
+ @tx.add_output(TransactionOutput.new(value: 50_000))
56
+ end
57
+
58
+ it "should have good defaults" do
59
+ @tx.inputs_amount.must_equal nil
60
+ @tx.fee.must_equal nil
61
+ @tx.outputs_amount.must_equal 50_123
62
+ end
63
+
64
+ it "should derive inputs_amount from fee" do
65
+ @tx.fee = 10_000
66
+ @tx.fee.must_equal 10_000
67
+ @tx.inputs_amount.must_equal 60_123
68
+ @tx.outputs_amount.must_equal 50_123
69
+ end
70
+
71
+ it "should derive fee from inputs_amount" do
72
+ @tx.inputs_amount = 55_123
73
+ @tx.fee.must_equal 5_000
74
+ @tx.inputs_amount.must_equal 55_123
75
+ @tx.outputs_amount.must_equal 50_123
76
+ end
77
+
78
+ it "should derive inputs_amount from input values if present" do
79
+ @tx.inputs[0].value = 50_523
80
+ @tx.inputs[1].value = 100
81
+ @tx.fee.must_equal 500
82
+ @tx.inputs_amount.must_equal 50_623
83
+ @tx.outputs_amount.must_equal 50_123
84
+ end
85
+
86
+ it "should not derive inputs_amount from input values if some value is nil" do
87
+ @tx.inputs[0].value = 50_523
88
+ @tx.inputs[1].value = nil
89
+ @tx.fee.must_equal nil
90
+ @tx.inputs_amount.must_equal nil
91
+ @tx.outputs_amount.must_equal 50_123
92
+ end
93
+ end
94
+
95
+
96
+ describe "Certain transaction" do
97
+
98
+ before do
99
+ @txdata =("0100000001dfb32e172d6cdc51215c28b83415f977fc6ce281e057f7cf40c700" +
100
+ "8003f7230f000000008a47304402207f5561ac3cfb05743cab6ca914f7eb93c4" +
101
+ "89f276f10cdf4549e7f0b0ef4e85cd02200191c0c2fd10f10158973a0344fdaf" +
102
+ "2438390e083a509d2870bcf2b05445612b0141043304596050ca119efccada1d" +
103
+ "d7ca8e511a76d8e1ddb7ad050298d208455b8bcd09593d823ca252355bf0b41c" +
104
+ "2ac0ba2afa7ada4660bd38e27585aac7d4e6e435ffffffff02c0791817000000" +
105
+ "0017a914bd224370f93a2b0435ded92c7f609e71992008fc87ac7b4d1d000000" +
106
+ "001976a914450c22770eebb00d376edabe7bb548aa64aa235688ac00000000").from_hex
107
+ @tx = Transaction.new(hex: @txdata.to_hex)
108
+ @tx = Transaction.new(data: @txdata)
109
+ end
110
+
111
+ it "should decode inputs and outputs correctly" do
112
+ @tx.version.must_equal 1
113
+ @tx.lock_time.must_equal 0
114
+ @tx.inputs.size.must_equal 1
115
+ @tx.outputs.size.must_equal 2
116
+ @tx.transaction_id.must_equal "f2d0daf07409e44216fe71075df88f3c8c0c5f8e313582ab256e7af2765dd14e"
117
+ end
118
+
119
+ it "should support dup" do
120
+ @tx2 = @tx.dup
121
+ @tx2.data.must_equal @txdata
122
+ @tx2.must_equal @tx
123
+ @tx2.object_id.wont_equal @tx.object_id
124
+ end
125
+
126
+ it "detect script kinds" do
127
+ @tx.outputs[0].script.standard?.must_equal true
128
+ @tx.outputs[0].script.script_hash_script?.must_equal true
129
+
130
+ @tx.outputs[1].script.standard?.must_equal true
131
+ @tx.outputs[1].script.public_key_hash_script?.must_equal true
132
+ end
133
+
134
+ it "input script should have a valid signature" do
135
+
136
+ @tx.inputs.first.signature_script.to_a.map{|c|c.to_hex}.must_equal [
137
+ "304402207f5561ac3cfb05743cab6ca914f7eb93c489f276f10cdf4549e7f0b0ef4e85cd02200191c0c2fd10f10158973a0344fdaf2438390e083a509d2870bcf2b05445612b01",
138
+ "043304596050ca119efccada1dd7ca8e511a76d8e1ddb7ad050298d208455b8bcd09593d823ca252355bf0b41c2ac0ba2afa7ada4660bd38e27585aac7d4e6e435"
139
+ ]
140
+
141
+ Diagnostics.current.trace do
142
+ BTC::Key.validate_script_signature(@tx.inputs.first.signature_script.to_a[0], verify_lower_s: true).must_equal true
143
+ end
144
+ end
145
+
146
+ end
147
+
148
+ describe "Coinbase Transaction" do
149
+ before do
150
+ @txdata =("0100000001000000000000000000000000000000000000000000000000000000" +
151
+ "0000000000ffffffff130301e6040654188d181202119700de00000fccffffff" +
152
+ "ff0108230595000000001976a914ca6ecc7d4d671d8c5c964a48dbb0bc194407" +
153
+ "a30688ac00000000").from_hex
154
+ @tx = Transaction.new(data: @txdata)
155
+ end
156
+
157
+ it "should encode coinbase inputs correctly" do
158
+ @tx.data.must_equal @txdata
159
+ end
160
+ end
161
+
162
+ end