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,205 @@
|
|
|
1
|
+
require 'bitcoin/script'
|
|
2
|
+
|
|
3
|
+
module Bitcoin
|
|
4
|
+
module Protocol
|
|
5
|
+
|
|
6
|
+
class Tx
|
|
7
|
+
|
|
8
|
+
# transaction hash
|
|
9
|
+
attr_reader :hash
|
|
10
|
+
|
|
11
|
+
# inputs (Array of TxIn)
|
|
12
|
+
attr_reader :in
|
|
13
|
+
|
|
14
|
+
# outputs (Array of TxOut)
|
|
15
|
+
attr_reader :out
|
|
16
|
+
|
|
17
|
+
# raw protocol payload
|
|
18
|
+
attr_reader :payload
|
|
19
|
+
|
|
20
|
+
# version (usually 1)
|
|
21
|
+
attr_accessor :ver
|
|
22
|
+
|
|
23
|
+
# lock time
|
|
24
|
+
attr_accessor :lock_time
|
|
25
|
+
|
|
26
|
+
alias :inputs :in
|
|
27
|
+
alias :outputs :out
|
|
28
|
+
|
|
29
|
+
# compare to another tx
|
|
30
|
+
def ==(other)
|
|
31
|
+
@hash == other.hash
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# return the tx hash in binary format
|
|
35
|
+
def binary_hash
|
|
36
|
+
[@hash].pack("H*").reverse
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# create tx from raw binary +data+
|
|
40
|
+
def initialize(data=nil)
|
|
41
|
+
@ver, @lock_time, @in, @out = 1, 0, [], []
|
|
42
|
+
parse_data(data) if data
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# generate the tx hash for given +payload+ in hex format
|
|
46
|
+
def hash_from_payload(payload)
|
|
47
|
+
Digest::SHA256.digest(Digest::SHA256.digest( payload )).reverse.unpack("H*")[0]
|
|
48
|
+
end
|
|
49
|
+
alias generate_hash hash_from_payload
|
|
50
|
+
|
|
51
|
+
# add an input
|
|
52
|
+
def add_in(input); (@in ||= []) << input; end
|
|
53
|
+
|
|
54
|
+
# add an output
|
|
55
|
+
def add_out(output); (@out ||= []) << output; end
|
|
56
|
+
|
|
57
|
+
# parse raw binary data
|
|
58
|
+
def parse_data(data)
|
|
59
|
+
@ver = data.unpack("I")[0]
|
|
60
|
+
idx = 4
|
|
61
|
+
in_size, tmp = Protocol.unpack_var_int(data[idx..-1])
|
|
62
|
+
idx += data[idx..-1].bytesize-tmp.bytesize
|
|
63
|
+
# raise "unkown transaction version: #{@ver}" unless @ver == 1
|
|
64
|
+
|
|
65
|
+
@in = (0...in_size).map{
|
|
66
|
+
txin = TxIn.new
|
|
67
|
+
idx += txin.parse_data(data[idx..-1])
|
|
68
|
+
txin
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
out_size, tmp = Protocol.unpack_var_int(data[idx..-1])
|
|
72
|
+
idx += data[idx..-1].bytesize-tmp.bytesize
|
|
73
|
+
|
|
74
|
+
@out = (0...out_size).map{
|
|
75
|
+
txout = TxOut.new
|
|
76
|
+
idx += txout.parse_data(data[idx..-1])
|
|
77
|
+
txout
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
@lock_time = data[idx...idx+=4].unpack("I")[0]
|
|
81
|
+
|
|
82
|
+
@payload = data[0...idx]
|
|
83
|
+
@hash = hash_from_payload(@payload)
|
|
84
|
+
|
|
85
|
+
if data[idx] == nil
|
|
86
|
+
true # reached the end.
|
|
87
|
+
else
|
|
88
|
+
data[idx..-1] # rest of buffer.
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# output transaction in raw binary format
|
|
93
|
+
def to_payload
|
|
94
|
+
pin = @in.map(&:to_payload).join
|
|
95
|
+
pout = @out.map(&:to_payload).join
|
|
96
|
+
|
|
97
|
+
in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
|
|
98
|
+
[[@ver].pack("I"), in_size, pin, out_size, pout, [@lock_time].pack("I")].join
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# generate a signature hash for input +input_idx+.
|
|
102
|
+
# either pass the +outpoint_tx+ or the +script_pubkey+ directly.
|
|
103
|
+
def signature_hash_for_input(input_idx, outpoint_tx, script_pubkey=nil, hash_type=nil, drop_sigs=nil, script=nil)
|
|
104
|
+
# https://github.com/bitcoin/bitcoin/blob/e071a3f6c06f41068ad17134189a4ac3073ef76b/script.cpp#L834
|
|
105
|
+
# http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Script.java#318
|
|
106
|
+
# https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
|
|
107
|
+
# https://github.com/bitcoin/bitcoin/blob/c2e8c8acd8ae0c94c70b59f55169841ad195bb99/src/script.cpp#L1058
|
|
108
|
+
|
|
109
|
+
hash_type ||= 1 # 1: ALL, 2: NONE, 3: SINGLE
|
|
110
|
+
|
|
111
|
+
pin = @in.map.with_index{|input,idx|
|
|
112
|
+
if idx == input_idx
|
|
113
|
+
script_pubkey ||= outpoint_tx.out[ input.prev_out_index ].pk_script
|
|
114
|
+
script_pubkey = Bitcoin::Script.binary_from_string(script) if script # force this string a script
|
|
115
|
+
script_pubkey = Bitcoin::Script.drop_signatures(script_pubkey, drop_sigs) if drop_sigs # array of signature to drop
|
|
116
|
+
input.to_payload(script_pubkey)
|
|
117
|
+
else
|
|
118
|
+
case hash_type
|
|
119
|
+
when 2; input.to_payload("", "\x00\x00\x00\x00")
|
|
120
|
+
else; input.to_payload("")
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
}.join
|
|
124
|
+
|
|
125
|
+
pout = @out.map(&:to_payload).join
|
|
126
|
+
|
|
127
|
+
case hash_type
|
|
128
|
+
when 2
|
|
129
|
+
pout = ""
|
|
130
|
+
in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(0)
|
|
131
|
+
else
|
|
132
|
+
in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
buf = [ [@ver].pack("I"), in_size, pin, out_size, pout, [@lock_time, hash_type].pack("II") ].join
|
|
136
|
+
Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# verify input signature +in_idx+ against the corresponding
|
|
140
|
+
# output in +outpoint_tx+
|
|
141
|
+
def verify_input_signature(in_idx, outpoint_tx)
|
|
142
|
+
outpoint_idx = @in[in_idx].prev_out_index
|
|
143
|
+
script_sig = @in[in_idx].script_sig
|
|
144
|
+
script_pubkey = outpoint_tx.out[outpoint_idx].pk_script
|
|
145
|
+
script = script_sig + script_pubkey
|
|
146
|
+
|
|
147
|
+
Bitcoin::Script.new(script).run do |pubkey,sig,hash_type,drop_sigs,script|
|
|
148
|
+
# this IS the checksig callback, must return true/false
|
|
149
|
+
hash = signature_hash_for_input(in_idx, outpoint_tx, nil, hash_type, drop_sigs, script)
|
|
150
|
+
#hash = signature_hash_for_input(in_idx, nil, script_pubkey, hash_type, drop_sigs, script)
|
|
151
|
+
Bitcoin.verify_signature( hash, sig, pubkey.unpack("H*")[0] )
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
# convert to ruby hash (see also #from_hash)
|
|
156
|
+
def to_hash
|
|
157
|
+
@hash ||= hash_from_payload(to_payload)
|
|
158
|
+
{
|
|
159
|
+
'hash' => @hash, 'ver' => @ver,
|
|
160
|
+
'vin_sz' => @in.size, 'vout_sz' => @out.size,
|
|
161
|
+
'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
|
|
162
|
+
'in' => @in.map(&:to_hash),
|
|
163
|
+
'out' => @out.map(&:to_hash)
|
|
164
|
+
}
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# generates rawblock json as seen in the block explorer.
|
|
168
|
+
def to_json(options = {:space => ''}, *a)
|
|
169
|
+
JSON.pretty_generate( to_hash, options )
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# write json representation to a file
|
|
173
|
+
# (see also #to_json)
|
|
174
|
+
def to_json_file(path)
|
|
175
|
+
File.open(path, 'wb'){|f| f.print to_json; }
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# parse ruby hash (see also #to_hash)
|
|
179
|
+
def self.from_hash(h)
|
|
180
|
+
tx = new(nil)
|
|
181
|
+
tx.ver, tx.lock_time = *h.values_at('ver', 'lock_time')
|
|
182
|
+
h['in'] .each{|input| tx.add_in TxIn.from_hash(input) }
|
|
183
|
+
h['out'].each{|output| tx.add_out TxOut.from_hash(output) }
|
|
184
|
+
tx.instance_eval{ @hash = hash_from_payload(@payload = to_payload) }
|
|
185
|
+
tx
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
# convert ruby hash to raw binary
|
|
189
|
+
def self.binary_from_hash(h); from_hash(h).to_payload; end
|
|
190
|
+
|
|
191
|
+
# parse json representation
|
|
192
|
+
def self.from_json(json_string); from_hash( JSON.load(json_string) ); end
|
|
193
|
+
|
|
194
|
+
# convert json representation to raw binary
|
|
195
|
+
def self.binary_from_json(json_string); from_json(json_string).to_payload; end
|
|
196
|
+
|
|
197
|
+
# read binary block from a file
|
|
198
|
+
def self.from_file(path); new( Bitcoin::Protocol.read_binary_file(path) ); end
|
|
199
|
+
|
|
200
|
+
# read json block from a file
|
|
201
|
+
def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class TxIn
|
|
5
|
+
|
|
6
|
+
# previous output hash
|
|
7
|
+
attr_accessor :prev_out
|
|
8
|
+
|
|
9
|
+
# previous output index
|
|
10
|
+
attr_accessor :prev_out_index
|
|
11
|
+
|
|
12
|
+
# script_sig input Script (signature)
|
|
13
|
+
attr_accessor :script_sig, :script_sig_length
|
|
14
|
+
|
|
15
|
+
# sequence
|
|
16
|
+
attr_accessor :sequence
|
|
17
|
+
|
|
18
|
+
DEFAULT_SEQUENCE = "\xff\xff\xff\xff"
|
|
19
|
+
|
|
20
|
+
def initialize *args
|
|
21
|
+
@prev_out, @prev_out_index, @script_sig_length,
|
|
22
|
+
@script_sig, @sequence = *args
|
|
23
|
+
@sequence ||= DEFAULT_SEQUENCE
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# compare to another txout
|
|
27
|
+
def ==(other)
|
|
28
|
+
@prev_out == other.prev_out &&
|
|
29
|
+
@prev_out_index == other.prev_out_index &&
|
|
30
|
+
@script_sig == other.script_sig &&
|
|
31
|
+
@sequence == other.sequence
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# parse raw binary data for transaction input
|
|
35
|
+
def parse_data(data)
|
|
36
|
+
idx = 0
|
|
37
|
+
@prev_out, @prev_out_index = data[idx...idx+=36].unpack("a32I")
|
|
38
|
+
@script_sig_length, tmp = Protocol.unpack_var_int(data[idx..-1])
|
|
39
|
+
idx += data[idx..-1].bytesize - tmp.bytesize
|
|
40
|
+
@script_sig = data[idx...idx+=@script_sig_length]
|
|
41
|
+
@sequence = data[idx...idx+=4]
|
|
42
|
+
idx
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alias :parse_payload :parse_data
|
|
46
|
+
|
|
47
|
+
def to_payload(script=@script_sig, sequence=@sequence)
|
|
48
|
+
buf = [ @prev_out, @prev_out_index ].pack("a32I")
|
|
49
|
+
buf << Protocol.pack_var_int(script.bytesize)
|
|
50
|
+
buf << script if script.bytesize > 0
|
|
51
|
+
buf << (sequence || DEFAULT_SEQUENCE)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def to_hash
|
|
55
|
+
t = { 'prev_out' => { 'hash' => @prev_out.reverse.unpack("H*")[0], 'n' => @prev_out_index } }
|
|
56
|
+
if coinbase?
|
|
57
|
+
t['coinbase'] = @script_sig.unpack("H*")[0]
|
|
58
|
+
else # coinbase tx
|
|
59
|
+
t['scriptSig'] = Bitcoin::Script.new(@script_sig).to_string
|
|
60
|
+
t['sequence'] = @sequence.unpack("I")[0] unless @sequence == "\xff\xff\xff\xff"
|
|
61
|
+
end
|
|
62
|
+
t
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.from_hash(input)
|
|
66
|
+
txin = TxIn.new([ input['prev_out']['hash'] ].pack('H*').reverse, input['prev_out']['n'])
|
|
67
|
+
if input['coinbase']
|
|
68
|
+
txin.script_sig = [ input['coinbase'] ].pack("H*")
|
|
69
|
+
txin.sequence = "\xff\xff\xff\xff"
|
|
70
|
+
else
|
|
71
|
+
txin.script_sig = Script.binary_from_string(input['scriptSig'])
|
|
72
|
+
txin.sequence = [ input['sequence'] || 0xffffffff ].pack("I")
|
|
73
|
+
end
|
|
74
|
+
txin
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# previous output in hex
|
|
78
|
+
def previous_output
|
|
79
|
+
@prev_out.reverse.unpack("H*")[0]
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# check if input is coinbase
|
|
83
|
+
def coinbase?
|
|
84
|
+
(@prev_out_index == 4294967295) && (@prev_out == "\x00"*32)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# set script_sig and script_sig_length
|
|
88
|
+
def script_sig=(script_sig)
|
|
89
|
+
@script_sig_length = script_sig.bytesize
|
|
90
|
+
@script_sig = script_sig
|
|
91
|
+
end
|
|
92
|
+
alias :script= :script_sig=
|
|
93
|
+
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class TxOut
|
|
5
|
+
|
|
6
|
+
# output value (in base units; "satoshi")
|
|
7
|
+
attr_accessor :value
|
|
8
|
+
|
|
9
|
+
# pk_script output Script
|
|
10
|
+
attr_accessor :pk_script, :pk_script_length
|
|
11
|
+
|
|
12
|
+
def initialize *args
|
|
13
|
+
if args.size == 2
|
|
14
|
+
@value, @pk_script_length, @pk_script = args[0], args[1].bytesize, args[1]
|
|
15
|
+
else
|
|
16
|
+
@value, @pk_script_length, @pk_script = *args
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def ==(other)
|
|
21
|
+
@value == other.value && @pk_script == other.pk_script
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# parse raw binary data for transaction output
|
|
25
|
+
def parse_data(data)
|
|
26
|
+
idx = 0
|
|
27
|
+
@value = data[idx...idx+=8].unpack("Q")[0]
|
|
28
|
+
@pk_script_length, tmp = Protocol.unpack_var_int(data[idx..-1])
|
|
29
|
+
idx += data[idx..-1].bytesize - tmp.bytesize
|
|
30
|
+
@pk_script = data[idx...idx+=@pk_script_length]
|
|
31
|
+
idx
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
alias :parse_payload :parse_data
|
|
35
|
+
|
|
36
|
+
def to_payload
|
|
37
|
+
buf = [ @value ].pack("Q")
|
|
38
|
+
buf << Protocol.pack_var_int(@pk_script_length)
|
|
39
|
+
buf << @pk_script if @pk_script_length > 0
|
|
40
|
+
buf
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def to_hash
|
|
44
|
+
{ 'value' => "%.8f" % (@value / 100000000.0),
|
|
45
|
+
'scriptPubKey' => Bitcoin::Script.new(@pk_script).to_string }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.from_hash(output)
|
|
49
|
+
amount = output['value'].gsub('.','').to_i
|
|
50
|
+
script = Script.binary_from_string(output['scriptPubKey'])
|
|
51
|
+
new(amount, script)
|
|
52
|
+
end
|
|
53
|
+
# set pk_script and pk_script_length
|
|
54
|
+
def pk_script=(script)
|
|
55
|
+
@pk_script_length, @pk_script = script.bytesize, script
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
alias :amount :value
|
|
59
|
+
alias :amount= :value=
|
|
60
|
+
alias :script :pk_script
|
|
61
|
+
alias :script= :pk_script=
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# create output spending +value+ btc (base units) to +address+
|
|
65
|
+
def self.value_to_address(value, address)
|
|
66
|
+
pk_script = Bitcoin::Script.to_address_script(address)
|
|
67
|
+
new(value, pk_script)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
module Bitcoin
|
|
2
|
+
module Protocol
|
|
3
|
+
|
|
4
|
+
class Version
|
|
5
|
+
attr_reader :fields
|
|
6
|
+
def initialize(opts={})
|
|
7
|
+
@fields = {
|
|
8
|
+
:version => Bitcoin::Protocol::VERSION,
|
|
9
|
+
:services => 1,
|
|
10
|
+
:time => Time.now.tv_sec,
|
|
11
|
+
:from => "127.0.0.1:8333",
|
|
12
|
+
:to => "127.0.0.1:8333",
|
|
13
|
+
:nonce => Bitcoin::Protocol::Uniq,
|
|
14
|
+
:user_agent => "/bitcoin-ruby:#{Bitcoin::VERSION}/",
|
|
15
|
+
:last_block => 0 # 188617
|
|
16
|
+
}.merge( opts.reject{|k,v| v == nil } )
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def to_payload
|
|
20
|
+
payload = [
|
|
21
|
+
@fields.values_at(:version, :services, :time).pack("IQQ"),
|
|
22
|
+
pack_address_field(@fields[:from]),
|
|
23
|
+
pack_address_field(@fields[:to]),
|
|
24
|
+
@fields.values_at(:nonce).pack("Q"),
|
|
25
|
+
Protocol.pack_var_string(@fields[:user_agent]),
|
|
26
|
+
@fields.values_at(:last_block).pack("I")
|
|
27
|
+
].join
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def to_pkt
|
|
31
|
+
Bitcoin::Protocol.pkt("version", to_payload)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def parse(payload)
|
|
35
|
+
version, services, timestamp, to, from, nonce, payload = payload.unpack("Ia8Qa26a26Qa*")
|
|
36
|
+
to, from = unpack_address_field(to), unpack_address_field(from)
|
|
37
|
+
user_agent, payload = Protocol.unpack_var_string(payload)
|
|
38
|
+
last_block = payload.unpack("I")[0]
|
|
39
|
+
|
|
40
|
+
@fields = {
|
|
41
|
+
:version => version, :services => services, :time => timestamp,
|
|
42
|
+
:from => from, :to => to, :nonce => nonce,
|
|
43
|
+
:user_agent => user_agent, :last_block => last_block
|
|
44
|
+
}
|
|
45
|
+
self
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def unpack_address_field(payload)
|
|
49
|
+
ip, port = payload.unpack("x8x12a4n")
|
|
50
|
+
"#{ip.unpack("C*").join(".")}:#{port}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pack_address_field(addr_str)
|
|
54
|
+
host, port = addr_str.split(":")
|
|
55
|
+
port = port ? port.to_i : 8333
|
|
56
|
+
sockaddr = Socket.pack_sockaddr_in(port, host)
|
|
57
|
+
#raise "invalid IPv4 Address: #{addr}" unless sockaddr[0...2] == "\x02\x00"
|
|
58
|
+
port, host = sockaddr[2...4], sockaddr[4...8]
|
|
59
|
+
[[1].pack("Q"), "\x00"*10, "\xFF\xFF", host, port].join
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def uptime
|
|
63
|
+
@fields[:time] - Time.now.tv_sec
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.parse(payload); new.parse(payload); end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
end
|
|
70
|
+
end
|