bitcoin-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|