btcruby 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,33 @@
1
+ require_relative 'spec_helper'
2
+ require_relative '../lib/btcruby/secp256k1'
3
+ describe BTC::Secp256k1 do
4
+
5
+ def verify_rfc6979_signature(keyhex, msg, sighex)
6
+ key = BTC::Key.new(private_key: keyhex.from_hex)
7
+ hash = BTC.sha256(msg)
8
+ sig = BTC::Secp256k1.ecdsa_signature(hash, key.private_key)
9
+ sig.to_hex.must_equal sighex
10
+ end
11
+
12
+ it "should produce deterministic ECDSA signatures Bitcoin-canonical using nonce from RFC6979" do
13
+ verify_rfc6979_signature("cca9fbcc1b41e5a95d369eaa6ddcff73b61a4efaa279cfc6567e8daa39cbaf50",
14
+ "sample",
15
+ "3045022100af340daf02cc15c8d5d08d7735dfe6b98a474ed373bdb5fbecf7571be52b384202205009fb27f37034a9b24b707b7c6b79ca23ddef9e25f7282e8a797efe53a8f124")
16
+ verify_rfc6979_signature("0000000000000000000000000000000000000000000000000000000000000001",
17
+ "Satoshi Nakamoto",
18
+ "3045022100934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d802202442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5")
19
+ verify_rfc6979_signature("fffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364140",
20
+ "Satoshi Nakamoto",
21
+ "3045022100fd567d121db66e382991534ada77a6bd3106f0a1098c231e47993447cd6af2d002206b39cd0eb1bc8603e159ef5c20a5c8ad685a45b06ce9bebed3f153d10d93bed5")
22
+ verify_rfc6979_signature("f8b8af8ce3c7cca5e300d33939540c10d45ce001b8f252bfbc57ba0342904181",
23
+ "Alan Turing",
24
+ "304402207063ae83e7f62bbb171798131b4a0564b956930092b33b07b395615d9ec7e15c022058dfcc1e00a35e1572f366ffe34ba0fc47db1e7189759b9fb233c5b05ab388ea")
25
+ verify_rfc6979_signature("0000000000000000000000000000000000000000000000000000000000000001",
26
+ "All those moments will be lost in time, like tears in rain. Time to die...",
27
+ "30450221008600dbd41e348fe5c9465ab92d23e3db8b98b873beecd930736488696438cb6b0220547fe64427496db33bf66019dacbf0039c04199abb0122918601db38a72cfc21")
28
+ verify_rfc6979_signature("e91671c46231f833a6406ccbea0e3e392c76c167bac1cb013f6f1013980455c2",
29
+ "There is a computer disease that anybody who works with computers knows about. It's a very serious disease and it interferes completely with the work. The trouble with computers is that you 'play' with them!",
30
+ "3045022100b552edd27580141f3b2a5463048cb7cd3e047b97c9f98076c32dbdf85a68718b0220279fa72dd19bfae05577e06c7c0c1900c371fcd5893f7e1d56a37d30174671f6")
31
+ end
32
+
33
+ end
@@ -6,3 +6,118 @@ require_relative '../lib/btcruby/extensions'
6
6
 
7
7
  # So every test can access classes directly without prefixing them with BTC::
8
8
  include BTC
9
+
10
+ # Script helper used by transaction_spec and script_interpreter_spec
11
+ FLAGS_MAP = {
12
+ "" => ScriptFlags::SCRIPT_VERIFY_NONE,
13
+ "NONE" => ScriptFlags::SCRIPT_VERIFY_NONE,
14
+ "P2SH" => ScriptFlags::SCRIPT_VERIFY_P2SH,
15
+ "STRICTENC" => ScriptFlags::SCRIPT_VERIFY_STRICTENC,
16
+ "DERSIG" => ScriptFlags::SCRIPT_VERIFY_DERSIG,
17
+ "LOW_S" => ScriptFlags::SCRIPT_VERIFY_LOW_S,
18
+ "NULLDUMMY" => ScriptFlags::SCRIPT_VERIFY_NULLDUMMY,
19
+ "SIGPUSHONLY" => ScriptFlags::SCRIPT_VERIFY_SIGPUSHONLY,
20
+ "MINIMALDATA" => ScriptFlags::SCRIPT_VERIFY_MINIMALDATA,
21
+ "DISCOURAGE_UPGRADABLE_NOPS" => ScriptFlags::SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_NOPS,
22
+ "CLEANSTACK" => ScriptFlags::SCRIPT_VERIFY_CLEANSTACK,
23
+ "CHECKLOCKTIMEVERIFY" => ScriptFlags::SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
24
+ }
25
+
26
+ def parse_script(json_script, expected_result = true)
27
+ # Note: individual 0xXX bytes may not be individually valid pushdatas, but will be valid when composed together.
28
+ # Since Script parses binary string right away, we need to compose all distinct bytes in a single hex string.
29
+ orig_string = json_script
30
+ json_script = json_script.dup
31
+ oldsize = json_script.size + 1
32
+ while json_script.size != oldsize
33
+ oldsize = json_script.size
34
+ json_script.gsub!(/0x([0-9a-fA-F]+)\s+0x/, "0x\\1")
35
+ end
36
+ json_script.split(" ").inject(Script.new) do |parsed_script, x|
37
+ if x.size == 0
38
+ # Empty string, ignore.
39
+ parsed_script
40
+ elsif x =~ /^-?\d+$/
41
+ # Number
42
+ n = x.to_i
43
+ if (n == -1) || (n >= 1 and n <= 16)
44
+ parsed_script << Opcode.opcode_for_small_integer(n)
45
+ else
46
+ parsed_script << ScriptNumber.new(integer: n).data
47
+ end
48
+ elsif x[0,2] == "0x"
49
+ # Raw hex data, inserted NOT pushed onto stack:
50
+ data = BTC.from_hex(x[2..-1])
51
+ Script.new(data: parsed_script.data + data)
52
+ elsif x =~ /^'.*'$/
53
+ # Single-quoted string, pushed as data.
54
+ parsed_script << x[1..-2]
55
+ else
56
+ # opcode, e.g. OP_ADD or ADD:
57
+ opcode = Opcode.opcode_for_name("OP_" + x)
58
+ opcode = Opcode.opcode_for_name(x) if opcode == OP_INVALIDOPCODE
59
+ parsed_script << opcode
60
+ end
61
+ end
62
+ rescue => e
63
+ if expected_result
64
+ # puts "json_script = #{orig_string.inspect}"
65
+ # puts "json_script = #{json_script.inspect}"
66
+ # puts "EXCEPTION: #{e}"
67
+ end
68
+ return nil
69
+ end
70
+
71
+ def parse_flags(string)
72
+ string.split(",").inject(0) do |flags, x|
73
+ f = FLAGS_MAP[x] or raise RuntimeError, "unrecognized script flag: #{x.inspect}"
74
+ flags | f
75
+ end
76
+ end
77
+
78
+ def build_crediting_transaction(scriptPubKey)
79
+ txCredit = Transaction.new;
80
+ txCredit.version = 1;
81
+ txCredit.lock_time = 0;
82
+ txCredit.add_input(TransactionInput.new(
83
+ previous_hash: nil,
84
+ coinbase_data: (Script.new << ScriptNumber.new(integer:0) << ScriptNumber.new(integer:0)).data
85
+ ))
86
+ txCredit.add_output(TransactionOutput.new(
87
+ script: scriptPubKey,
88
+ value: 0
89
+ ))
90
+ txCredit
91
+ end
92
+
93
+ def build_spending_transaction(scriptSig, txCredit)
94
+ txSpend = Transaction.new
95
+ txSpend.version = 1;
96
+ txSpend.lock_time = 0;
97
+ txSpend.add_input(TransactionInput.new(
98
+ previous_hash: txCredit.transaction_hash,
99
+ previous_index: 0,
100
+ signature_script: scriptSig
101
+ ))
102
+ txSpend.add_output(TransactionOutput.new(
103
+ script: Script.new,
104
+ value: 0
105
+ ))
106
+ txSpend
107
+ end
108
+
109
+ # ctx = build_crediting_transaction(Script.new)
110
+ # stx = build_spending_transaction(Script.new, build_crediting_transaction(Script.new))
111
+ #
112
+ # puts "crediting tx: #{ctx.transaction_id}" # 7f33a2f5ace097f071010d5105e7fd01f22c83d8d5daa741a41f2a630a2af23b
113
+ # puts "spending tx: #{stx.transaction_id}" # add55eb99bb1f653ab822ea4177cb0f9673bcc5c2c4c729894ab0c626c8fa1e1
114
+ #
115
+ # puts "crediting tx: #{ctx.data.to_hex}"
116
+ # puts "spending tx: #{stx.data.to_hex}"
117
+ # From Bitcoin Core:
118
+ # ctxdummy: 01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff020000ffffffff0100000000000000000000000000;
119
+ # ID = 7f33a2f5ace097f071010d5105e7fd01f22c83d8d5daa741a41f2a630a2af23b
120
+ # stxdummy: 01000000013bf22a0a632a1fa441a7dad5d8832cf201fde705510d0171f097e0acf5a2337f0000000000ffffffff0100000000000000000000000000;
121
+ # ID = add55eb99bb1f653ab822ea4177cb0f9673bcc5c2c4c729894ab0c626c8fa1e1
122
+
123
+
@@ -1,4 +1,7 @@
1
1
  require_relative 'spec_helper'
2
+ require_relative 'data/tx_valid'
3
+ require_relative 'data/tx_invalid'
4
+
2
5
  describe BTC::Transaction do
3
6
 
4
7
  it "should have core attributes" do
@@ -19,6 +22,156 @@ describe BTC::Transaction do
19
22
  })
20
23
 
21
24
  end
25
+
26
+ describe "Bitcoin Core test vectors" do
27
+
28
+ module TxTestHelper
29
+ extend self
30
+
31
+ # Read tests from test/data/tx_valid.json
32
+ # Format is an array of arrays
33
+ # Inner arrays are either [ "comment" ]
34
+ # or [[[prevout hash, prevout index, prevout scriptPubKey], [input 2], ...],"], serializedTransaction, verifyFlags
35
+ # ... where all scripts are stringified scripts.
36
+ #
37
+ # verifyFlags is a comma separated list of script verification flags to apply, or "NONE"
38
+ def parse_tests(records, expected_result)
39
+ comment = nil
40
+ records.each do |test|
41
+ if test[0].is_a?(Array)
42
+ if test.size != 3 || !test[1].is_a?(String) || !test[2].is_a?(String)
43
+ raise "Bad test: #{test.inspect} (#{test.size} #{test[1].class} #{test[2].class})"
44
+ end
45
+ mapprevOutScriptPubKeys = {} # Outpoint => Script
46
+ inputs = test[0]
47
+ inputs.each do |input|
48
+ raise "Bad test: input is not an array: #{test.inspect}" if !input.is_a?(Array)
49
+ raise "Bad test: input is an array of 3 items: #{test.inspect}" if input.size != 3
50
+ previd, previndex, scriptstring = input
51
+
52
+ outpoint = Outpoint.new(transaction_id: previd, index: previndex)
53
+
54
+ mapprevOutScriptPubKeys[outpoint] = parse_script(scriptstring)
55
+ end
56
+
57
+ tx = Transaction.new(hex: test[1])
58
+ flags = parse_flags(test[2])
59
+
60
+ if debug_filter(test)
61
+ validation_proc = lambda do
62
+ validation_passed = Validation.new.check_transaction(tx, ValidationState.new)
63
+ if expected_result
64
+ validation_passed.must_equal expected_result
65
+ end
66
+ script_passed = false
67
+
68
+ if validation_passed
69
+ tx.inputs.each do |txin|
70
+ output_script = mapprevOutScriptPubKeys[txin.outpoint]
71
+ raise "Bad test: output script not found: #{test.inspect}" if !output_script
72
+ sig_script = txin.signature_script
73
+ if !sig_script
74
+ sig_script = Script.new(data: txin.coinbase_data)
75
+ end
76
+
77
+ checker = TransactionSignatureChecker.new(transaction: tx, input_index: txin.index)
78
+ plugins = []
79
+ plugins << P2SHPlugin.new if (flags & ScriptFlags::SCRIPT_VERIFY_P2SH) != 0
80
+ plugins << CLTVPlugin.new if (flags & ScriptFlags::SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) != 0
81
+ interpreter = ScriptInterpreter.new(
82
+ flags: flags,
83
+ plugins: plugins,
84
+ signature_checker: checker,
85
+ raise_on_failure: expected_result,
86
+ )
87
+ #Diagnostics.current.trace do
88
+ script_passed = interpreter.verify_script(signature_script: sig_script, output_script: output_script)
89
+ if !script_passed
90
+ break
91
+ end
92
+ #end
93
+ end
94
+ end
95
+ (script_passed && validation_passed).must_equal expected_result
96
+ end # proc
97
+
98
+ yield(comment || test.inspect, validation_proc)
99
+ end # if not filtered
100
+
101
+ comment = nil
102
+ else
103
+ comment ||= ""
104
+ comment += test[0].gsub(/\.$/,"") + ". "
105
+ comment.gsub!(/\. $/, "")
106
+ end
107
+ end
108
+ end
109
+
110
+ def debug_filter(test)
111
+
112
+ #return test.inspect[%{010000000200010000000000000000000000000000000000000000000000000000000000000000000000ffffffff00020000000000000000000000000000000000000000000000000000000000000100000000000000000100000000000000000000000000}]
113
+
114
+
115
+ # !!! SIGHASH_SINGLE tx: afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae hashed for input 0: 1eccdc1f7a4783924a49113b491a847de2f89a1e7d73b1ae561d80f918035f46
116
+ # !!! SIGHASH_SINGLE tx: afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae hashed for input 1: 1943e87af64d0bde608a85330f09aa5c9887a4fdfd9ca6d7a139bef27fee8e3b
117
+ # !!! SIGHASH_SINGLE tx: afd9c17f8913577ec3509520bd6e5d63e9c0fd2a5f70c787993b097ba6ca9fae hashed for input 2: 1c1f068da6a721f2ecb0fdac3b8adcb4073fee34506971472d29d305507894d6
118
+ #return test.inspect[%{DUP HASH160 0x14 0xdcf72c4fd02f5a987cf9b02f2fabfcac3341a87d EQUALVERIFY CHECKSIG}]
119
+
120
+ #return test.inspect[%{[[["60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1", 0, "1 0x41 0x04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4 0x41 0x0461cbdcc5409fb4b4d42b51d33381354d80e550078cb532a34bfa2fcfdeb7d76519aecc62770f5b0e4ef8551946d8a540911abe3e7854a26f39f58b25c15342af 2 OP_CHECKMULTISIG"]], "0100000001b14bdcbc3e01bdaad36cc08e81e69c82e1060bc14e518db2b49aa43ad90ba26000000000490047304402203f16c6f40162ab686621ef3000b04e75418a0c0cb2d8aebeac894ae360ac1e780220ddc15ecdfc3507ac48e1681a33eb60996631bf6bf5bc0a0682c4db743ce7ca2b01ffffffff0140420f00000000001976a914660d4ef3a743e3e696ad990364e555c271ad504b88ac00000000", "P2SH"]}]
121
+ true
122
+ end
123
+
124
+ # def verify_script(tx, txin, sig_script, output_script, flags, expected_result, record)
125
+ # checker = TransactionSignatureChecker.new(transaction: tx, input_index: txin.index)
126
+ # plugins = []
127
+ # plugins << P2SHPlugin.new if (flags & ScriptFlags::SCRIPT_VERIFY_P2SH) != 0
128
+ # plugins << CLTVPlugin.new if (flags & ScriptFlags::SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY) != 0
129
+ # interpreter = ScriptInterpreter.new(
130
+ # flags: flags,
131
+ # plugins: plugins,
132
+ # signature_checker: checker,
133
+ # raise_on_failure: expected_result,
134
+ # )
135
+ # #Diagnostics.current.trace do
136
+ # checked = Validation.new.check_transaction(tx, ValidationState.new)
137
+ # if expected_result
138
+ # checked.must_equal expected_result
139
+ # end
140
+ # result = false
141
+ # if checked
142
+ # result = interpreter.verify_script(signature_script: sig_script, output_script: output_script)
143
+ # if result != expected_result
144
+ # # puts "Failed scripts: #{sig_script.to_s.inspect} #{output_script.to_s.inspect} flags #{flags}, expected to #{expected_result ? 'succeed' : 'fail'}".gsub(/OP_/, "")
145
+ # # puts "Error: #{interpreter.error.inspect}"
146
+ # #debug("Failed #{expected_result ? 'valid' : 'invalid'} script: #{sig_script.to_s.inspect} #{output_script.to_s.inspect} flags #{flags} -- #{record.inspect}")
147
+ # end
148
+ # end
149
+ # puts
150
+ # puts record.inspect
151
+ # puts "---------------------------"
152
+ # puts (result && checked).inspect
153
+ # puts
154
+ # (result && checked).must_equal expected_result
155
+ # #end
156
+ # end
157
+
158
+
159
+ end
160
+
161
+ TxTestHelper.parse_tests(ValidTxs, true) do |comment, validation_proc|
162
+ it "should validate transaction: #{comment}" do
163
+ validation_proc.call
164
+ #TxTestHelper.verify_script(tx, txin, signature_script, output_script, flags, expected_result, record)
165
+ end
166
+ end
167
+
168
+ TxTestHelper.parse_tests(InvalidTxs, false) do |comment, validation_proc| # |helper, tx, txin, signature_script, output_script, flags, expected_result, record, comment|
169
+ it "should fail transaction: #{comment}" do
170
+ validation_proc.call
171
+ #TxTestHelper.verify_script(tx, txin, signature_script, output_script, flags, expected_result, record)
172
+ end
173
+ end
174
+ end
22
175
 
23
176
  describe "Hash <-> ID conversion" do
24
177
  before do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: btcruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oleg Andreev
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-07-30 00:00:00.000000000 Z
12
+ date: 2015-08-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: ffi
@@ -101,18 +101,23 @@ files:
101
101
  - lib/btcruby/open_assets/asset_transaction_output.rb
102
102
  - lib/btcruby/open_assets/issuance_id.rb
103
103
  - lib/btcruby/openssl.rb
104
+ - lib/btcruby/outpoint.rb
104
105
  - lib/btcruby/proof_of_work.rb
105
106
  - lib/btcruby/safety.rb
107
+ - lib/btcruby/script/cltv_plugin.rb
106
108
  - lib/btcruby/script/opcode.rb
109
+ - lib/btcruby/script/p2sh_plugin.rb
107
110
  - lib/btcruby/script/script.rb
108
111
  - lib/btcruby/script/script_error.rb
109
112
  - lib/btcruby/script/script_flags.rb
110
113
  - lib/btcruby/script/script_interpreter.rb
114
+ - lib/btcruby/script/script_interpreter_plugin.rb
111
115
  - lib/btcruby/script/script_number.rb
112
116
  - lib/btcruby/script/signature_checker.rb
113
117
  - lib/btcruby/script/signature_hashtype.rb
114
118
  - lib/btcruby/script/test_signature_checker.rb
115
119
  - lib/btcruby/script/transaction_signature_checker.rb
120
+ - lib/btcruby/secp256k1.rb
116
121
  - lib/btcruby/transaction.rb
117
122
  - lib/btcruby/transaction_builder.rb
118
123
  - lib/btcruby/transaction_builder/errors.rb
@@ -120,8 +125,8 @@ files:
120
125
  - lib/btcruby/transaction_builder/result.rb
121
126
  - lib/btcruby/transaction_builder/signer.rb
122
127
  - lib/btcruby/transaction_input.rb
123
- - lib/btcruby/transaction_outpoint.rb
124
128
  - lib/btcruby/transaction_output.rb
129
+ - lib/btcruby/validation.rb
125
130
  - lib/btcruby/version.rb
126
131
  - lib/btcruby/wif.rb
127
132
  - lib/btcruby/wire_format.rb
@@ -131,6 +136,10 @@ files:
131
136
  - spec/block_header_spec.rb
132
137
  - spec/block_spec.rb
133
138
  - spec/currency_formatter_spec.rb
139
+ - spec/data/script_invalid.rb
140
+ - spec/data/script_valid.rb
141
+ - spec/data/tx_invalid.rb
142
+ - spec/data/tx_valid.rb
134
143
  - spec/data_spec.rb
135
144
  - spec/diagnostics_spec.rb
136
145
  - spec/key_spec.rb
@@ -145,7 +154,9 @@ files:
145
154
  - spec/open_assets/asset_transaction_spec.rb
146
155
  - spec/open_assets/issuance_id_spec.rb
147
156
  - spec/proof_of_work_spec.rb
157
+ - spec/script_interpreter_spec.rb
148
158
  - spec/script_spec.rb
159
+ - spec/secp256k1_spec.rb
149
160
  - spec/spec_helper.rb
150
161
  - spec/transaction_builder_spec.rb
151
162
  - spec/transaction_spec.rb
@@ -194,7 +205,9 @@ test_files:
194
205
  - spec/open_assets/asset_transaction_spec.rb
195
206
  - spec/open_assets/issuance_id_spec.rb
196
207
  - spec/proof_of_work_spec.rb
208
+ - spec/script_interpreter_spec.rb
197
209
  - spec/script_spec.rb
210
+ - spec/secp256k1_spec.rb
198
211
  - spec/transaction_builder_spec.rb
199
212
  - spec/transaction_spec.rb
200
213
  - spec/wire_format_spec.rb