bitcoin-ruby 0.0.1
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.
- data/.gitignore +12 -0
- data/COPYING +18 -0
- data/Gemfile +4 -0
- data/README.rdoc +189 -0
- data/Rakefile +104 -0
- data/bin/bitcoin_dns_seed +130 -0
- data/bin/bitcoin_gui +80 -0
- data/bin/bitcoin_node +174 -0
- data/bin/bitcoin_shell +12 -0
- data/bin/bitcoin_wallet +323 -0
- data/bitcoin-ruby.gemspec +27 -0
- data/concept-examples/blockchain-pow.rb +151 -0
- data/doc/CONFIG.rdoc +66 -0
- data/doc/EXAMPLES.rdoc +9 -0
- data/doc/NODE.rdoc +35 -0
- data/doc/STORAGE.rdoc +21 -0
- data/doc/WALLET.rdoc +102 -0
- data/examples/balance.rb +60 -0
- data/examples/bbe_verify_tx.rb +55 -0
- data/examples/connect.rb +36 -0
- data/examples/relay_tx.rb +22 -0
- data/examples/verify_tx.rb +57 -0
- data/lib/bitcoin.rb +370 -0
- data/lib/bitcoin/builder.rb +266 -0
- data/lib/bitcoin/config.rb +56 -0
- data/lib/bitcoin/connection.rb +126 -0
- data/lib/bitcoin/ffi/openssl.rb +121 -0
- data/lib/bitcoin/gui/addr_view.rb +42 -0
- data/lib/bitcoin/gui/bitcoin-ruby.png +0 -0
- data/lib/bitcoin/gui/bitcoin-ruby.svg +80 -0
- data/lib/bitcoin/gui/conn_view.rb +36 -0
- data/lib/bitcoin/gui/connection.rb +68 -0
- data/lib/bitcoin/gui/em_gtk.rb +28 -0
- data/lib/bitcoin/gui/gui.builder +1643 -0
- data/lib/bitcoin/gui/gui.rb +290 -0
- data/lib/bitcoin/gui/helpers.rb +113 -0
- data/lib/bitcoin/gui/tree_view.rb +82 -0
- data/lib/bitcoin/gui/tx_view.rb +67 -0
- data/lib/bitcoin/key.rb +125 -0
- data/lib/bitcoin/logger.rb +65 -0
- data/lib/bitcoin/network/command_client.rb +93 -0
- data/lib/bitcoin/network/command_handler.rb +179 -0
- data/lib/bitcoin/network/connection_handler.rb +274 -0
- data/lib/bitcoin/network/node.rb +399 -0
- data/lib/bitcoin/protocol.rb +140 -0
- data/lib/bitcoin/protocol/address.rb +48 -0
- data/lib/bitcoin/protocol/alert.rb +47 -0
- data/lib/bitcoin/protocol/block.rb +154 -0
- data/lib/bitcoin/protocol/handler.rb +38 -0
- data/lib/bitcoin/protocol/parser.rb +148 -0
- data/lib/bitcoin/protocol/tx.rb +205 -0
- data/lib/bitcoin/protocol/txin.rb +97 -0
- data/lib/bitcoin/protocol/txout.rb +73 -0
- data/lib/bitcoin/protocol/version.rb +70 -0
- data/lib/bitcoin/script.rb +634 -0
- data/lib/bitcoin/storage/dummy.rb +164 -0
- data/lib/bitcoin/storage/models.rb +133 -0
- data/lib/bitcoin/storage/sequel.rb +335 -0
- data/lib/bitcoin/storage/sequel_store/sequel_migrations.rb +84 -0
- data/lib/bitcoin/storage/storage.rb +243 -0
- data/lib/bitcoin/version.rb +3 -0
- data/lib/bitcoin/wallet/coinselector.rb +30 -0
- data/lib/bitcoin/wallet/keygenerator.rb +75 -0
- data/lib/bitcoin/wallet/keystore.rb +203 -0
- data/lib/bitcoin/wallet/txdp.rb +116 -0
- data/lib/bitcoin/wallet/wallet.rb +243 -0
- data/spec/bitcoin/bitcoin_spec.rb +472 -0
- data/spec/bitcoin/builder_spec.rb +90 -0
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +27 -0
- data/spec/bitcoin/fixtures/23b397edccd3740a74adb603c9756370fafcde9bcc4483eb271ecad09a94dd63.json +23 -0
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +27 -0
- data/spec/bitcoin/fixtures/60a20bd93aa49ab4b28d514ec10b06e1829ce6818ec06cd3aabd013ebcdc4bb1.json +45 -0
- data/spec/bitcoin/fixtures/bc179baab547b7d7c1d5d8d6f8b0cc6318eaa4b0dd0a093ad6ac7f5a1cb6b3ba.json +34 -0
- data/spec/bitcoin/fixtures/rawblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-0.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-1.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-1.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-131025.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-131025.json +5063 -0
- data/spec/bitcoin/fixtures/rawblock-170.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-170.json +68 -0
- data/spec/bitcoin/fixtures/rawblock-9.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-9.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-26478.json +64 -0
- data/spec/bitcoin/fixtures/rawtx-01.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-01.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-02.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-02.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-03.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-03.json +48 -0
- data/spec/bitcoin/fixtures/rawtx-04.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-05.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-14be6fff8c6014f7c9493b4a6e4a741699173f39d74431b6b844fcb41ebb9984.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-2f4a2717ec8c9f077a87dde6cbe0274d5238793a3f3f492b63c744837285e58a.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-406b2b06bcd34d3c8733e6b79f7a394c8a431fbf4ff5ac705c93f4076bb77602.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-52250a162c7d03d2e1fbc5ebd1801a88612463314b55102171c5b5d817d2d7b2.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-b5d4e8883533f99e5903ea2cf001a133a322fa6b1370b18a16c57c946a40823d.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-ba1ff5cd66713133c062a871a8adab92416f1e38d17786b2bf56ac5f6ffdfdf5.json +37 -0
- data/spec/bitcoin/fixtures/rawtx-c99c49da4c38af669dea436d3e73780dfdb6c1ecf9958baa52960e8baee30e73.json +24 -0
- data/spec/bitcoin/fixtures/rawtx-de35d060663750b3975b7997bde7fb76307cec5b270d12fcd9c4ad98b279c28c.json +23 -0
- data/spec/bitcoin/fixtures/rawtx-f4184fc596403b9d638783cf57adfe4c75c605f6356fbc91338530e9831e9e16.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-a220adf1902c46a39db25a24bc4178b6a88440f977a7e2cabfdd8b5c1dd35cfb.json +27 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-testnet-e232e0055dbdca88bbaa79458683195a0b7c17c5b6c524a8d146721d4d4d652f.json +41 -0
- data/spec/bitcoin/fixtures/reorg/blk_0_to_4.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_3A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_4A.dat +0 -0
- data/spec/bitcoin/fixtures/reorg/blk_5A.dat +0 -0
- data/spec/bitcoin/fixtures/testnet/block_0.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_1.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_2.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_3.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_4.bin +0 -0
- data/spec/bitcoin/fixtures/testnet/block_5.bin +0 -0
- data/spec/bitcoin/fixtures/txdp-1.txt +32 -0
- data/spec/bitcoin/fixtures/txdp-2-signed.txt +19 -0
- data/spec/bitcoin/fixtures/txdp-2-unsigned.txt +14 -0
- data/spec/bitcoin/key_spec.rb +123 -0
- data/spec/bitcoin/network_spec.rb +48 -0
- data/spec/bitcoin/protocol/addr_spec.rb +68 -0
- data/spec/bitcoin/protocol/alert_spec.rb +20 -0
- data/spec/bitcoin/protocol/block_spec.rb +101 -0
- data/spec/bitcoin/protocol/inv_spec.rb +124 -0
- data/spec/bitcoin/protocol/ping_spec.rb +49 -0
- data/spec/bitcoin/protocol/tx_spec.rb +226 -0
- data/spec/bitcoin/protocol/version_spec.rb +77 -0
- data/spec/bitcoin/reorg_spec.rb +129 -0
- data/spec/bitcoin/script/opcodes_spec.rb +417 -0
- data/spec/bitcoin/script/script_spec.rb +246 -0
- data/spec/bitcoin/spec_helper.rb +36 -0
- data/spec/bitcoin/storage_spec.rb +229 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +35 -0
- data/spec/bitcoin/wallet/keygenerator_spec.rb +64 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +188 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +74 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +207 -0
- metadata +295 -0
|
@@ -0,0 +1,634 @@
|
|
|
1
|
+
require 'bitcoin'
|
|
2
|
+
|
|
3
|
+
class Bitcoin::Script
|
|
4
|
+
|
|
5
|
+
OP_1 = 81
|
|
6
|
+
OP_TRUE = 81
|
|
7
|
+
OP_0 = 0
|
|
8
|
+
OP_FALSE = 0
|
|
9
|
+
OP_PUSHDATA1 = 76
|
|
10
|
+
OP_PUSHDATA2 = 77
|
|
11
|
+
OP_PUSHDATA4 = 78
|
|
12
|
+
OP_NOP = 97
|
|
13
|
+
OP_DUP = 118
|
|
14
|
+
OP_HASH160 = 169
|
|
15
|
+
OP_EQUAL = 135
|
|
16
|
+
OP_VERIFY = 105
|
|
17
|
+
OP_EQUALVERIFY = 136
|
|
18
|
+
OP_CHECKSIG = 172
|
|
19
|
+
OP_CHECKSIGVERIFY = 173
|
|
20
|
+
OP_CHECKMULTISIG = 174
|
|
21
|
+
OP_CHECKMULTISIGVERIFY = 175
|
|
22
|
+
OP_TOALTSTACK = 107
|
|
23
|
+
OP_FROMALTSTACK = 108
|
|
24
|
+
OP_TUCK = 125
|
|
25
|
+
OP_SWAP = 124
|
|
26
|
+
OP_BOOLAND = 154
|
|
27
|
+
OP_ADD = 147
|
|
28
|
+
OP_SUB = 148
|
|
29
|
+
OP_GREATERTHANOREQUAL = 162
|
|
30
|
+
OP_DROP = 117
|
|
31
|
+
OP_HASH256 = 170
|
|
32
|
+
OP_SHA256 = 168
|
|
33
|
+
OP_SHA1 = 167
|
|
34
|
+
OP_RIPEMD160 = 166
|
|
35
|
+
OP_EVAL = 176
|
|
36
|
+
OP_NOP2 = 177
|
|
37
|
+
OP_CHECKHASHVERIFY = 177
|
|
38
|
+
OP_CODESEPARATOR = 171
|
|
39
|
+
OP_MIN = 163
|
|
40
|
+
OP_MAX = 164
|
|
41
|
+
OP_2OVER = 112
|
|
42
|
+
OP_2SWAP = 114
|
|
43
|
+
OP_IFDUP = 115
|
|
44
|
+
OP_DEPTH = 116
|
|
45
|
+
OP_1NEGATE = 79
|
|
46
|
+
# OP_IF = 99
|
|
47
|
+
# OP_NOTIF = 100
|
|
48
|
+
# OP_ELSE = 103
|
|
49
|
+
# OP_ENDIF = 104
|
|
50
|
+
|
|
51
|
+
OPCODES = Hash[*constants.grep(/^OP_/).map{|i| [const_get(i), i.to_s] }.flatten]
|
|
52
|
+
OPCODES[0] = "0"
|
|
53
|
+
OPCODES[81] = "1"
|
|
54
|
+
|
|
55
|
+
OPCODES_ALIAS = {
|
|
56
|
+
"OP_TRUE" => OP_1,
|
|
57
|
+
"OP_FALSE" => OP_0,
|
|
58
|
+
"OP_NOP1" => OP_EVAL,
|
|
59
|
+
"OP_NOP2" => OP_CHECKHASHVERIFY
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
OP_2_16 = (82..96).to_a
|
|
63
|
+
|
|
64
|
+
attr_reader :raw, :chunks, :debug
|
|
65
|
+
|
|
66
|
+
# create a new script. +bytes+ is typically input_script + output_script
|
|
67
|
+
def initialize(bytes, offset=0)
|
|
68
|
+
@raw = bytes
|
|
69
|
+
@stack, @stack_alt = [], []
|
|
70
|
+
@chunks = parse(bytes, offset)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# parse raw script
|
|
74
|
+
def parse(bytes, offset=0)
|
|
75
|
+
program = bytes.unpack("C*")
|
|
76
|
+
chunks = []
|
|
77
|
+
until program.empty?
|
|
78
|
+
opcode = program.shift(1)[0]
|
|
79
|
+
if opcode >= 0xf0
|
|
80
|
+
opcode = (opcode << 8) | program.shift(1)[0]
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
if (opcode > 0) && (opcode < OP_PUSHDATA1)
|
|
84
|
+
len = opcode
|
|
85
|
+
chunks << program.shift(len).pack("C*")
|
|
86
|
+
elsif (opcode == OP_PUSHDATA1)
|
|
87
|
+
len = program.shift(1)[0]
|
|
88
|
+
chunks << program.shift(len).pack("C*")
|
|
89
|
+
elsif (opcode == OP_PUSHDATA2)
|
|
90
|
+
len = program.shift(2).pack("C*").unpack("n")[0]
|
|
91
|
+
chunks << program.shift(len).pack("C*")
|
|
92
|
+
elsif (opcode == OP_PUSHDATA4)
|
|
93
|
+
len = program.shift(4).pack("C*").unpack("N")[0]
|
|
94
|
+
chunks << program.shift(len).pack("C*")
|
|
95
|
+
else
|
|
96
|
+
chunks << opcode
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
chunks
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# string representation of the script
|
|
103
|
+
def to_string(chunks=nil)
|
|
104
|
+
(chunks || @chunks).map{|i|
|
|
105
|
+
case i
|
|
106
|
+
when Fixnum
|
|
107
|
+
case i
|
|
108
|
+
when *OPCODES.keys; OPCODES[i]
|
|
109
|
+
when *OP_2_16; (OP_2_16.index(i)+2).to_s
|
|
110
|
+
else "(opcode #{i})"
|
|
111
|
+
end
|
|
112
|
+
when String
|
|
113
|
+
i.unpack("H*")[0]
|
|
114
|
+
end
|
|
115
|
+
}.join(" ")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# script object of a string representation
|
|
119
|
+
def self.from_string(script_string)
|
|
120
|
+
new(binary_from_string(script_string))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
class ScriptOpcodeError < StandardError; end
|
|
124
|
+
|
|
125
|
+
# raw script binary of a string representation
|
|
126
|
+
def self.binary_from_string(script_string)
|
|
127
|
+
script_string.split(" ").map{|i|
|
|
128
|
+
case i
|
|
129
|
+
when /^OP_PUSHDATA[124]$/; # skip
|
|
130
|
+
when *OPCODES.values; OPCODES.find{|k,v| v == i }.first
|
|
131
|
+
when *OPCODES_ALIAS.keys; OPCODES_ALIAS.find{|k,v| k == i }.last
|
|
132
|
+
when /^([2-9]|1[0-6])$/; OP_2_16[$1.to_i-2]
|
|
133
|
+
when /\(opcode (\d+)\)/; $1.to_i
|
|
134
|
+
when /OP_(.+)$/; raise ScriptOpcodeError, "#{i} not defined!"
|
|
135
|
+
else
|
|
136
|
+
data = [i].pack("H*")
|
|
137
|
+
size = data.bytesize
|
|
138
|
+
|
|
139
|
+
head = if size < OP_PUSHDATA1
|
|
140
|
+
[size].pack("C")
|
|
141
|
+
elsif size > OP_PUSHDATA1 && size <= 0xff
|
|
142
|
+
[OP_PUSHDATA1, size].pack("CC")
|
|
143
|
+
elsif size > 0xff && size <= 0xffff
|
|
144
|
+
[OP_PUSHDATA2, size].pack("Cv")
|
|
145
|
+
elsif size > 0xffff && size <= 0xffffffff
|
|
146
|
+
[OP_PUSHDATA4, size].pack("CV")
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
head + data
|
|
150
|
+
end
|
|
151
|
+
}.map{|i|
|
|
152
|
+
i.is_a?(Fixnum) ? [i].pack("C*") : i # TODO yikes, implement/pack 2 byte opcodes.
|
|
153
|
+
}.join
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def invalid?
|
|
157
|
+
@script_invalid ||= false
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
# run the script. +check_callback+ is called for OP_CHECKSIG operations
|
|
161
|
+
def run(&check_callback)
|
|
162
|
+
return pay_to_script_hash(check_callback) if is_p2sh?
|
|
163
|
+
@debug = []
|
|
164
|
+
@chunks.each{|chunk|
|
|
165
|
+
break if invalid?
|
|
166
|
+
@debug << @stack.map{|i| i.unpack("H*") rescue i}
|
|
167
|
+
|
|
168
|
+
case chunk
|
|
169
|
+
when Fixnum
|
|
170
|
+
case chunk
|
|
171
|
+
|
|
172
|
+
when *OPCODES_METHOD.keys
|
|
173
|
+
m = method( n=OPCODES_METHOD[chunk] )
|
|
174
|
+
@debug << n.to_s.upcase
|
|
175
|
+
(m.arity == 1) ? m.call(check_callback) : m.call # invoke opcode method
|
|
176
|
+
|
|
177
|
+
when *OP_2_16
|
|
178
|
+
@stack << OP_2_16.index(chunk) + 2
|
|
179
|
+
@debug << "OP_#{chunk-80}"
|
|
180
|
+
else
|
|
181
|
+
name = OPCODES[chunk] || chunk
|
|
182
|
+
raise "opcode #{name} unkown or not implemented"
|
|
183
|
+
end
|
|
184
|
+
when String
|
|
185
|
+
@debug << "PUSH DATA #{chunk.unpack("H*")[0]}"
|
|
186
|
+
@stack << chunk
|
|
187
|
+
end
|
|
188
|
+
}
|
|
189
|
+
@debug << @stack.map{|i| i.unpack("H*") rescue i }
|
|
190
|
+
|
|
191
|
+
if @script_invalid
|
|
192
|
+
@stack << 0
|
|
193
|
+
@debug << "INVALID TRANSACTION"
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
@debug << "RESULT"
|
|
197
|
+
return false if @stack.empty?
|
|
198
|
+
return false if @stack.pop == 0
|
|
199
|
+
true
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def invalid
|
|
203
|
+
@script_invalid = true; nil
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
def codehash_script(opcode)
|
|
207
|
+
# CScript scriptCode(pbegincodehash, pend);
|
|
208
|
+
script = to_string(@chunks[(@codehash_start||0)...@chunks.size-@chunks.reverse.index(opcode)])
|
|
209
|
+
checkhash = Bitcoin.hash160(Bitcoin::Script.binary_from_string(script).unpack("H*")[0])
|
|
210
|
+
[script, checkhash]
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def self.drop_signatures(script_pubkey, drop_signatures)
|
|
214
|
+
script = new(script_pubkey).to_string.split(" ").delete_if{|c| drop_signatures.include?(c) }.join(" ")
|
|
215
|
+
script_pubkey = binary_from_string(script)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# pay_to_script_hash: https://en.bitcoin.it/wiki/BIP_0016
|
|
219
|
+
#
|
|
220
|
+
# <sig> {<pub> OP_CHECKSIG} | OP_HASH160 <script_hash> OP_EQUAL
|
|
221
|
+
def pay_to_script_hash(check_callback)
|
|
222
|
+
return false unless @chunks.size == 5
|
|
223
|
+
script_hash = @chunks[-2]
|
|
224
|
+
script = @chunks[-4]
|
|
225
|
+
sig = self.class.from_string(@chunks[0].unpack("H*")[0]).raw
|
|
226
|
+
|
|
227
|
+
return false unless Bitcoin.hash160(script.unpack("H*")[0]) == script_hash.unpack("H*")[0]
|
|
228
|
+
script = self.class.new(sig + script)
|
|
229
|
+
script.run(&check_callback)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def is_pay_to_script_hash?
|
|
233
|
+
@chunks.size >= 3 && @chunks[-3] == OP_HASH160 &&
|
|
234
|
+
@chunks[-2].bytesize == 20 && @chunks[-1] == OP_EQUAL
|
|
235
|
+
end
|
|
236
|
+
alias :is_p2sh? :is_pay_to_script_hash?
|
|
237
|
+
|
|
238
|
+
# check if script is in one of the recognized standard formats
|
|
239
|
+
def is_standard?
|
|
240
|
+
is_pubkey? || is_hash160? || is_multisig? || is_p2sh?
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# is this a pubkey tx
|
|
244
|
+
def is_pubkey?
|
|
245
|
+
return false if @chunks.size != 2
|
|
246
|
+
(@chunks[1] == OP_CHECKSIG) && @chunks[0].size > 1
|
|
247
|
+
end
|
|
248
|
+
alias :is_send_to_ip? :is_pubkey?
|
|
249
|
+
|
|
250
|
+
# is this a hash160 (address) tx
|
|
251
|
+
def is_hash160?
|
|
252
|
+
return false if @chunks.size != 5
|
|
253
|
+
(@chunks[0..1] + @chunks[-2..-1]) ==
|
|
254
|
+
[OP_DUP, OP_HASH160, OP_EQUALVERIFY, OP_CHECKSIG] &&
|
|
255
|
+
@chunks[2].is_a?(String) && @chunks[2].bytesize == 20
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
# is this a multisig tx
|
|
259
|
+
def is_multisig?
|
|
260
|
+
return false if @chunks.size > 6 || @chunks.size < 4
|
|
261
|
+
@chunks[-1] == OP_CHECKMULTISIG
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def type
|
|
265
|
+
if is_hash160?; :hash160
|
|
266
|
+
elsif is_pubkey?; :pubkey
|
|
267
|
+
elsif is_multisig?; :multisig
|
|
268
|
+
elsif is_p2sh?; :p2sh
|
|
269
|
+
else; :unknown
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# get the public key for this pubkey script
|
|
274
|
+
def get_pubkey
|
|
275
|
+
return @chunks[0].unpack("H*")[0] if @chunks.size == 1
|
|
276
|
+
is_pubkey? ? @chunks[0].unpack("H*")[0] : nil
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# get the pubkey address for this pubkey script
|
|
280
|
+
def get_pubkey_address
|
|
281
|
+
Bitcoin.pubkey_to_address(get_pubkey)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
# get the hash160 for this hash160 script
|
|
285
|
+
def get_hash160
|
|
286
|
+
return @chunks[2..-3][0].unpack("H*")[0] if is_hash160?
|
|
287
|
+
return Bitcoin.hash160(get_pubkey) if is_pubkey?
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# get the hash160 address for this hash160 script
|
|
291
|
+
def get_hash160_address
|
|
292
|
+
Bitcoin.hash160_to_address(get_hash160)
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
# get the public keys for this multisig script
|
|
296
|
+
def get_multisig_pubkeys
|
|
297
|
+
1.upto(@chunks[-2] - 80).map {|i| @chunks[i]}
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# get the pubkey addresses for this multisig script
|
|
301
|
+
def get_multisig_addresses
|
|
302
|
+
get_multisig_pubkeys.map {|p| Bitcoin::Key.new(nil, p.unpack("H*")[0]).addr}
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# get all addresses this script corresponds to (if possible)
|
|
306
|
+
def get_addresses
|
|
307
|
+
return [get_pubkey_address] if is_pubkey?
|
|
308
|
+
return [get_hash160_address] if is_hash160?
|
|
309
|
+
return get_multisig_addresses if is_multisig?
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
# get single address, or first for multisig script
|
|
313
|
+
def get_address
|
|
314
|
+
addrs = get_addresses
|
|
315
|
+
addrs.is_a?(Array) ? addrs[0] : addrs
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
# generate pubkey tx script for given +pubkey+
|
|
319
|
+
def self.to_pubkey_script(pubkey)
|
|
320
|
+
pk = [pubkey].pack("H*")
|
|
321
|
+
[[pk.bytesize].pack("C"), pk, "\xAC"].join
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
# generate hash160 tx for given +address+
|
|
325
|
+
def self.to_hash160_script(hash160)
|
|
326
|
+
return nil unless hash160
|
|
327
|
+
# DUP HASH160 length hash160 EQUALVERIFY CHECKSIG
|
|
328
|
+
[ ["76", "a9", "14", hash160, "88", "ac"].join ].pack("H*")
|
|
329
|
+
end
|
|
330
|
+
|
|
331
|
+
def self.to_p2sh_script(p2sh)
|
|
332
|
+
return nil unless p2sh
|
|
333
|
+
# HASH160 length hash EQUAL
|
|
334
|
+
[ ["a9", "14", p2sh, "87"].join ].pack("H*")
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
def self.to_address_script(address)
|
|
338
|
+
hash160 = Bitcoin.hash160_from_address(address)
|
|
339
|
+
case Bitcoin.address_type(address)
|
|
340
|
+
when :hash160; to_hash160_script(hash160)
|
|
341
|
+
when :p2sh; to_p2sh_script(hash160)
|
|
342
|
+
end
|
|
343
|
+
end
|
|
344
|
+
|
|
345
|
+
# generate multisig tx for given +pubkeys+, expecting +m+ signatures
|
|
346
|
+
def self.to_multisig_script(m, *pubkeys)
|
|
347
|
+
pubs = pubkeys.map{|pk|p=[pk].pack("H*"); [p.bytesize].pack("C") + p}
|
|
348
|
+
[ [80 + m.to_i].pack("C"), *pubs, [80 + pubs.size].pack("C"), "\xAE"].join
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
# generate pubkey script sig for given +signature+ and +pubkey+
|
|
352
|
+
def self.to_pubkey_script_sig(signature, pubkey)
|
|
353
|
+
hash_type = "\x01"
|
|
354
|
+
#pubkey = [pubkey].pack("H*") if pubkey.bytesize != 65
|
|
355
|
+
raise "pubkey is not in binary form" unless pubkey.bytesize == 65 && pubkey[0] == "\x04"
|
|
356
|
+
[ [signature.bytesize+1].pack("C"), signature, hash_type, [pubkey.bytesize].pack("C"), pubkey ].join
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# alias for #to_pubkey_script_sig
|
|
360
|
+
def self.to_signature_pubkey_script(*a)
|
|
361
|
+
to_pubkey_script_sig(*a)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def self.to_multisig_script_sig(*sigs)
|
|
365
|
+
from_string("0 #{sigs.map{|s|s.unpack('H*')[0]}.join(' ')}").raw
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def get_signatures_required
|
|
369
|
+
return false unless is_multisig?
|
|
370
|
+
@chunks[0] - 80
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
## OPCODES
|
|
374
|
+
|
|
375
|
+
# Does nothing
|
|
376
|
+
def op_nop
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Duplicates the top stack item.
|
|
380
|
+
def op_dup
|
|
381
|
+
@stack << (@stack[-1].dup rescue @stack[-1])
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
# The input is hashed using SHA-256.
|
|
385
|
+
def op_sha256
|
|
386
|
+
buf = @stack.pop
|
|
387
|
+
@stack << Digest::SHA256.digest(buf)
|
|
388
|
+
end
|
|
389
|
+
|
|
390
|
+
# The input is hashed using SHA-1.
|
|
391
|
+
def op_sha1
|
|
392
|
+
buf = @stack.pop
|
|
393
|
+
@stack << Digest::SHA1.digest(buf)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
# The input is hashed twice: first with SHA-256 and then with RIPEMD-160.
|
|
397
|
+
def op_hash160
|
|
398
|
+
buf = @stack.pop
|
|
399
|
+
@stack << Digest::RMD160.digest(Digest::SHA256.digest(buf))
|
|
400
|
+
end
|
|
401
|
+
|
|
402
|
+
# The input is hashed using RIPEMD-160.
|
|
403
|
+
def op_ripemd160
|
|
404
|
+
buf = @stack.pop
|
|
405
|
+
@stack << Digest::RMD160.digest(buf)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
# The input is hashed two times with SHA-256.
|
|
409
|
+
def op_hash256
|
|
410
|
+
buf = @stack.pop
|
|
411
|
+
@stack << Digest::SHA256.digest(Digest::SHA256.digest(buf))
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
# Puts the input onto the top of the alt stack. Removes it from the main stack.
|
|
415
|
+
def op_toaltstack
|
|
416
|
+
@stack_alt << @stack.pop
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Puts the input onto the top of the main stack. Removes it from the alt stack.
|
|
420
|
+
def op_fromaltstack
|
|
421
|
+
@stack << @stack_alt.pop
|
|
422
|
+
end
|
|
423
|
+
|
|
424
|
+
# The item at the top of the stack is copied and inserted before the second-to-top item.
|
|
425
|
+
def op_tuck
|
|
426
|
+
@stack[-2..-1] = [ @stack[-1], *@stack[-2..-1] ]
|
|
427
|
+
end
|
|
428
|
+
|
|
429
|
+
# The top two items on the stack are swapped.
|
|
430
|
+
def op_swap
|
|
431
|
+
@stack[-2..-1] = @stack[-2..-1].reverse
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
# If both a and b are not 0, the output is 1. Otherwise 0.
|
|
435
|
+
def op_booland
|
|
436
|
+
a, b = @stack.pop(2)
|
|
437
|
+
@stack << (![a,b].any?{|n| n == 0 } ? 1 : 0)
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
# a is added to b.
|
|
441
|
+
def op_add
|
|
442
|
+
a, b = @stack.pop(2).reverse
|
|
443
|
+
@stack << a + b
|
|
444
|
+
end
|
|
445
|
+
|
|
446
|
+
# b is subtracted from a.
|
|
447
|
+
def op_sub
|
|
448
|
+
a, b = @stack.pop(2).reverse
|
|
449
|
+
@stack << a - b
|
|
450
|
+
end
|
|
451
|
+
|
|
452
|
+
# Returns 1 if a is greater than or equal to b, 0 otherwise.
|
|
453
|
+
def op_greaterthanorequal
|
|
454
|
+
a, b = @stack.pop(2).reverse
|
|
455
|
+
@stack << (a >= b ? 1 : 0)
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Removes the top stack item.
|
|
459
|
+
def op_drop
|
|
460
|
+
@stack.pop
|
|
461
|
+
end
|
|
462
|
+
|
|
463
|
+
# Returns 1 if the inputs are exactly equal, 0 otherwise.
|
|
464
|
+
def op_equal
|
|
465
|
+
a, b = @stack.pop(2).reverse
|
|
466
|
+
@stack << (a == b ? 1 : 0)
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
# Marks transaction as invalid if top stack value is not true. True is removed, but false is not.
|
|
470
|
+
def op_verify
|
|
471
|
+
res = @stack.pop
|
|
472
|
+
if res == 0
|
|
473
|
+
@stack << res
|
|
474
|
+
@script_invalid = true # raise 'transaction invalid' ?
|
|
475
|
+
else
|
|
476
|
+
@script_invalid = false
|
|
477
|
+
end
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
# Same as OP_EQUAL, but runs OP_VERIFY afterward.
|
|
481
|
+
def op_equalverify
|
|
482
|
+
op_equal; op_verify
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# An empty array of bytes is pushed onto the stack.
|
|
486
|
+
def op_0
|
|
487
|
+
@stack << "" # []
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# The number 1 is pushed onto the stack. Same as OP_TRUE
|
|
491
|
+
def op_1
|
|
492
|
+
@stack << 1
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Returns the smaller of a and b.
|
|
496
|
+
def op_min
|
|
497
|
+
@stack << @stack.pop(2).min
|
|
498
|
+
end
|
|
499
|
+
|
|
500
|
+
# Returns the larger of a and b.
|
|
501
|
+
def op_max
|
|
502
|
+
@stack << @stack.pop(2).max
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
# Copies the pair of items two spaces back in the stack to the front.
|
|
506
|
+
def op_2over
|
|
507
|
+
@stack << @stack[-4]
|
|
508
|
+
@stack << @stack[-4]
|
|
509
|
+
end
|
|
510
|
+
|
|
511
|
+
# Swaps the top two pairs of items.
|
|
512
|
+
def op_2swap
|
|
513
|
+
p1 = @stack.pop(2)
|
|
514
|
+
p2 = @stack.pop(2)
|
|
515
|
+
@stack += p1 += p2
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
# If the input is true, duplicate it.
|
|
519
|
+
def op_ifdup
|
|
520
|
+
if @stack.last != 0
|
|
521
|
+
@stack << @stack.last
|
|
522
|
+
end
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
# The number -1 is pushed onto the stack.
|
|
526
|
+
def op_1negate
|
|
527
|
+
@stack << -1
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# Puts the number of stack items onto the stack.
|
|
531
|
+
def op_depth
|
|
532
|
+
@stack << @stack.size
|
|
533
|
+
end
|
|
534
|
+
|
|
535
|
+
# https://en.bitcoin.it/wiki/BIP_0017 (old OP_NOP2)
|
|
536
|
+
# TODO: don't rely on it yet. add guards from wikipage too.
|
|
537
|
+
def op_checkhashverify
|
|
538
|
+
unless @checkhash && (@checkhash == @stack[-1].unpack("H*")[0])
|
|
539
|
+
@script_invalid = true
|
|
540
|
+
end
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
# All of the signature checking words will only match signatures
|
|
544
|
+
# to the data after the most recently-executed OP_CODESEPARATOR.
|
|
545
|
+
def op_codeseparator
|
|
546
|
+
@codehash_start = @chunks.size - @chunks.reverse.index(OP_CODESEPARATOR)
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
# do a CHECKSIG operation on the current stack,
|
|
550
|
+
# asking +check_callback+ to do the actual signature verification.
|
|
551
|
+
# This is used by Protocol::Tx#verify_input_signature
|
|
552
|
+
def op_checksig(check_callback)
|
|
553
|
+
return invalid if @stack.size < 2
|
|
554
|
+
pubkey = @stack.pop
|
|
555
|
+
drop_sigs = [@stack[-1].unpack("H*")[0]]
|
|
556
|
+
sig, hash_type = parse_sig(@stack.pop)
|
|
557
|
+
|
|
558
|
+
if @chunks.include?(OP_CHECKHASHVERIFY)
|
|
559
|
+
# Subset of script starting at the most recent codeseparator to OP_CHECKSIG
|
|
560
|
+
script_code, @checkhash = codehash_script(OP_CHECKSIG)
|
|
561
|
+
else
|
|
562
|
+
script_code, drop_sigs = nil, nil
|
|
563
|
+
end
|
|
564
|
+
|
|
565
|
+
if check_callback == nil # for tests
|
|
566
|
+
@stack << 1
|
|
567
|
+
else # real signature check callback
|
|
568
|
+
@stack <<
|
|
569
|
+
((check_callback.call(pubkey, sig, hash_type, drop_sigs, script_code) == true) ? 1 : 0)
|
|
570
|
+
end
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
def op_checksigverify(check_callback)
|
|
574
|
+
op_checksig(check_callback)
|
|
575
|
+
op_verify
|
|
576
|
+
end
|
|
577
|
+
|
|
578
|
+
# do a CHECKMULTISIG operation on the current stack,
|
|
579
|
+
# asking +check_callback+ to do the actual signature verification.
|
|
580
|
+
#
|
|
581
|
+
# CHECKMULTISIG does a m-of-n signatures verification on scripts of the form:
|
|
582
|
+
# 0 <sig1> <sig2> | 2 <pub1> <pub2> 2 OP_CHECKMULTISIG
|
|
583
|
+
# 0 <sig1> <sig2> | 2 <pub1> <pub2> <pub3> 3 OP_CHECKMULTISIG
|
|
584
|
+
# 0 <sig1> <sig2> <sig3> | 3 <pub1> <pub2> <pub3> 3 OP_CHECKMULTISIG
|
|
585
|
+
#
|
|
586
|
+
# see https://en.bitcoin.it/wiki/BIP_0011 for details.
|
|
587
|
+
# see https://github.com/bitcoin/bitcoin/blob/master/src/script.cpp#L931
|
|
588
|
+
#
|
|
589
|
+
# TODO: validate signature order
|
|
590
|
+
# TODO: take global opcode count
|
|
591
|
+
def op_checkmultisig(check_callback)
|
|
592
|
+
n_pubkeys = @stack.pop
|
|
593
|
+
return invalid unless (0..20).include?(n_pubkeys)
|
|
594
|
+
return invalid unless @stack.last(n_pubkeys).all?{|e| e.is_a?(String) && e != '' }
|
|
595
|
+
#return invalid if ((@op_count ||= 0) += n_pubkeys) > 201
|
|
596
|
+
pubkeys = @stack.pop(n_pubkeys)
|
|
597
|
+
|
|
598
|
+
n_sigs = @stack.pop
|
|
599
|
+
return invalid unless (0..n_pubkeys).include?(n_sigs)
|
|
600
|
+
return invalid unless @stack.last(n_sigs).all?{|e| e.is_a?(String) && e != '' }
|
|
601
|
+
sigs = (drop_sigs = @stack.pop(n_sigs)).map{|s| parse_sig(s) }
|
|
602
|
+
|
|
603
|
+
@stack.pop if @stack[-1] == '' # remove OP_NOP from stack
|
|
604
|
+
|
|
605
|
+
if @chunks.include?(OP_CHECKHASHVERIFY)
|
|
606
|
+
# Subset of script starting at the most recent codeseparator to OP_CHECKMULTISIG
|
|
607
|
+
script_code, @checkhash = codehash_script(OP_CHECKMULTISIG)
|
|
608
|
+
drop_sigs.map!{|i| i.unpack("H*")[0] }
|
|
609
|
+
else
|
|
610
|
+
script_code, drop_sigs = nil, nil
|
|
611
|
+
end
|
|
612
|
+
|
|
613
|
+
valid_sigs = 0
|
|
614
|
+
sigs.each{|sig, hash_type| pubkeys.each{|pubkey|
|
|
615
|
+
valid_sigs += 1 if check_callback.call(pubkey, sig, hash_type, drop_sigs, script_code)
|
|
616
|
+
}}
|
|
617
|
+
|
|
618
|
+
@stack << ((valid_sigs == n_sigs) ? 1 : (invalid; 0))
|
|
619
|
+
end
|
|
620
|
+
|
|
621
|
+
OPCODES_METHOD = Hash[*instance_methods.grep(/^op_/).map{|m|
|
|
622
|
+
[ (OPCODES.find{|k,v| v == m.to_s.upcase }.first rescue nil), m ]
|
|
623
|
+
}.flatten]
|
|
624
|
+
OPCODES_METHOD[0] = :op_0
|
|
625
|
+
OPCODES_METHOD[81] = :op_1
|
|
626
|
+
|
|
627
|
+
private
|
|
628
|
+
|
|
629
|
+
def parse_sig(sig)
|
|
630
|
+
hash_type = sig[-1].unpack("C")[0]
|
|
631
|
+
sig = sig[0...-1]
|
|
632
|
+
return sig, hash_type
|
|
633
|
+
end
|
|
634
|
+
end
|