bitcoin-ruby 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -1
- data/Gemfile +21 -0
- data/README.rdoc +85 -25
- data/Rakefile +7 -3
- data/bin/bitcoin_node +39 -42
- data/bin/bitcoin_shell +1 -0
- data/bin/bitcoin_wallet +129 -53
- data/bitcoin-ruby.gemspec +4 -7
- data/concept-examples/blockchain-pow.rb +1 -1
- data/doc/CONFIG.rdoc +5 -5
- data/doc/EXAMPLES.rdoc +9 -5
- data/doc/NAMECOIN.rdoc +34 -0
- data/doc/NODE.rdoc +147 -10
- data/examples/balance.rb +10 -4
- data/examples/bbe_verify_tx.rb +7 -2
- data/examples/forwarder.rb +73 -0
- data/examples/generate_tx.rb +34 -0
- data/examples/simple_network_monitor_and_util.rb +187 -0
- data/examples/verify_tx.rb +1 -1
- data/lib/bitcoin.rb +308 -18
- data/lib/bitcoin/builder.rb +62 -36
- data/lib/bitcoin/config.rb +2 -0
- data/lib/bitcoin/connection.rb +11 -8
- data/lib/bitcoin/electrum/mnemonic.rb +162 -0
- data/lib/bitcoin/ffi/openssl.rb +187 -21
- data/lib/bitcoin/gui/addr_view.rb +2 -0
- data/lib/bitcoin/gui/conn_view.rb +2 -0
- data/lib/bitcoin/gui/connection.rb +2 -0
- data/lib/bitcoin/gui/em_gtk.rb +2 -0
- data/lib/bitcoin/gui/gui.rb +2 -0
- data/lib/bitcoin/gui/helpers.rb +2 -0
- data/lib/bitcoin/gui/tree_view.rb +2 -0
- data/lib/bitcoin/gui/tx_view.rb +2 -0
- data/lib/bitcoin/key.rb +77 -11
- data/lib/bitcoin/litecoin.rb +81 -0
- data/lib/bitcoin/logger.rb +20 -1
- data/lib/bitcoin/namecoin.rb +279 -0
- data/lib/bitcoin/network/command_client.rb +7 -6
- data/lib/bitcoin/network/command_handler.rb +229 -43
- data/lib/bitcoin/network/connection_handler.rb +182 -70
- data/lib/bitcoin/network/node.rb +231 -106
- data/lib/bitcoin/protocol.rb +44 -23
- data/lib/bitcoin/protocol/address.rb +5 -3
- data/lib/bitcoin/protocol/alert.rb +3 -4
- data/lib/bitcoin/protocol/aux_pow.rb +123 -0
- data/lib/bitcoin/protocol/block.rb +98 -18
- data/lib/bitcoin/protocol/handler.rb +6 -5
- data/lib/bitcoin/protocol/parser.rb +44 -19
- data/lib/bitcoin/protocol/tx.rb +105 -52
- data/lib/bitcoin/protocol/txin.rb +39 -19
- data/lib/bitcoin/protocol/txout.rb +28 -13
- data/lib/bitcoin/protocol/version.rb +16 -7
- data/lib/bitcoin/script.rb +579 -122
- data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
- data/lib/bitcoin/storage/models.rb +20 -7
- data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
- data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
- data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
- data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
- data/lib/bitcoin/storage/storage.rb +233 -28
- data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
- data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
- data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
- data/lib/bitcoin/validation.rb +369 -0
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/coinselector.rb +3 -0
- data/lib/bitcoin/wallet/keygenerator.rb +3 -1
- data/lib/bitcoin/wallet/keystore.rb +6 -2
- data/lib/bitcoin/wallet/txdp.rb +6 -4
- data/lib/bitcoin/wallet/wallet.rb +54 -16
- data/spec/bitcoin/bitcoin_spec.rb +48 -3
- data/spec/bitcoin/builder_spec.rb +40 -17
- data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
- data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
- data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
- data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
- data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
- data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
- data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
- data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
- data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
- data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
- data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
- data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
- data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
- data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
- data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
- data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
- data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
- data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
- data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
- data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
- data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
- data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
- data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
- data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
- data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
- data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
- data/spec/bitcoin/key_spec.rb +128 -3
- data/spec/bitcoin/namecoin_spec.rb +182 -0
- data/spec/bitcoin/network_spec.rb +5 -3
- data/spec/bitcoin/node/command_api_spec.rb +376 -0
- data/spec/bitcoin/protocol/addr_spec.rb +2 -0
- data/spec/bitcoin/protocol/alert_spec.rb +2 -0
- data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
- data/spec/bitcoin/protocol/block_spec.rb +134 -39
- data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
- data/spec/bitcoin/protocol/inv_spec.rb +10 -8
- data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
- data/spec/bitcoin/protocol/ping_spec.rb +2 -0
- data/spec/bitcoin/protocol/tx_spec.rb +83 -17
- data/spec/bitcoin/protocol/version_spec.rb +7 -5
- data/spec/bitcoin/script/opcodes_spec.rb +412 -133
- data/spec/bitcoin/script/script_spec.rb +112 -13
- data/spec/bitcoin/spec_helper.rb +68 -0
- data/spec/bitcoin/storage/reorg_spec.rb +199 -0
- data/spec/bitcoin/storage/storage_spec.rb +337 -0
- data/spec/bitcoin/storage/validation_spec.rb +261 -0
- data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
- data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
- data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
- data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
- data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
- metadata +105 -51
- data/lib/bitcoin/storage/sequel.rb +0 -335
- data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
- data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
- data/spec/bitcoin/reorg_spec.rb +0 -129
- data/spec/bitcoin/storage_spec.rb +0 -229
@@ -1,21 +1,23 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
module Bitcoin
|
2
4
|
module Protocol
|
3
5
|
|
4
6
|
class Handler
|
5
7
|
def on_inv_transaction(hash)
|
6
|
-
p ['inv transaction', hth
|
8
|
+
p ['inv transaction', hash.hth]
|
7
9
|
end
|
8
10
|
|
9
11
|
def on_inv_block(hash)
|
10
|
-
p ['inv block', hth
|
12
|
+
p ['inv block', hash.hth]
|
11
13
|
end
|
12
14
|
|
13
15
|
def on_get_transaction(hash)
|
14
|
-
p ['get transaction', hth
|
16
|
+
p ['get transaction', hash.hth]
|
15
17
|
end
|
16
18
|
|
17
19
|
def on_get_block(hash)
|
18
|
-
p ['get block', hth
|
20
|
+
p ['get block', hash.hth]
|
19
21
|
end
|
20
22
|
|
21
23
|
def on_addr(addr)
|
@@ -31,7 +33,6 @@ module Bitcoin
|
|
31
33
|
puts block.to_json
|
32
34
|
end
|
33
35
|
|
34
|
-
def hth(h); h.unpack("H*")[0]; end
|
35
36
|
end
|
36
37
|
|
37
38
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
module Bitcoin
|
2
4
|
module Protocol
|
3
5
|
|
@@ -37,8 +39,6 @@ module Bitcoin
|
|
37
39
|
}
|
38
40
|
end
|
39
41
|
|
40
|
-
def hth(h); h.unpack("H*")[0]; end
|
41
|
-
|
42
42
|
def parse_addr(payload)
|
43
43
|
count, payload = Protocol.unpack_var_int(payload)
|
44
44
|
payload.each_byte.each_slice(30){|i|
|
@@ -52,18 +52,19 @@ module Bitcoin
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def parse_headers(payload)
|
55
|
-
|
56
|
-
|
57
|
-
|
55
|
+
return unless @h.respond_to?(:on_headers)
|
56
|
+
buf = StringIO.new(payload)
|
57
|
+
count = Protocol.unpack_var_int_from_io(buf)
|
58
|
+
headers = count.times.map{ b = Block.new; b.parse_data_from_io(buf, header_only=true); b }
|
58
59
|
@h.on_headers(headers)
|
59
60
|
end
|
60
61
|
|
61
62
|
def parse_getblocks(payload)
|
62
|
-
version, payload = payload.unpack('
|
63
|
+
version, payload = payload.unpack('Va*')
|
63
64
|
count, payload = Protocol.unpack_var_int(payload)
|
64
65
|
buf, payload = payload.unpack("a#{count*32}a*")
|
65
|
-
hashes = buf.each_byte.each_slice(32).map{|i| hash =
|
66
|
-
stop_hash =
|
66
|
+
hashes = buf.each_byte.each_slice(32).map{|i| hash = i.reverse.pack("C32").hth }
|
67
|
+
stop_hash = payload[0..32].reverse_hth
|
67
68
|
[version, hashes, stop_hash]
|
68
69
|
end
|
69
70
|
|
@@ -75,21 +76,24 @@ module Bitcoin
|
|
75
76
|
when 'inv'; parse_inv(payload, :put)
|
76
77
|
when 'getdata'; parse_inv(payload, :get)
|
77
78
|
when 'addr'; parse_addr(payload)
|
78
|
-
when '
|
79
|
+
when 'getaddr'; @h.on_getaddr if @h.respond_to?(:on_getaddr)
|
80
|
+
when 'verack'; @h.respond_to?(:on_verack) ? @h.on_verack : (@h.respond_to?(:on_handshake_complete) ? @h.on_handshake_complete : nil)
|
79
81
|
when 'version'; parse_version(payload)
|
80
82
|
when 'alert'; parse_alert(payload)
|
81
83
|
when 'ping'; @h.on_ping(payload.unpack("Q")[0])
|
82
84
|
when 'pong'; @h.on_pong(payload.unpack("Q")[0])
|
83
|
-
when 'getblocks'; @h.on_getblocks(*parse_getblocks(payload))
|
84
|
-
when 'getheaders'; @h.on_getheaders(*parse_getblocks(payload))
|
85
|
+
when 'getblocks'; @h.on_getblocks(*parse_getblocks(payload)) if @h.respond_to?(:on_getblocks)
|
86
|
+
when 'getheaders'; @h.on_getheaders(*parse_getblocks(payload)) if @h.respond_to?(:on_getheaders)
|
87
|
+
when 'mempool'; handle_mempool_request(payload)
|
88
|
+
when 'notfound'; handle_notfound_reply(payload)
|
85
89
|
else
|
86
|
-
p ['
|
90
|
+
p ['unknown-packet', command, payload]
|
87
91
|
end
|
88
92
|
end
|
89
93
|
|
90
94
|
def parse_version(payload)
|
91
|
-
version = Bitcoin::Protocol::Version.parse(payload)
|
92
|
-
@h.on_version(version)
|
95
|
+
@version = Bitcoin::Protocol::Version.parse(payload)
|
96
|
+
@h.on_version(@version)
|
93
97
|
end
|
94
98
|
|
95
99
|
def parse_alert(payload)
|
@@ -97,6 +101,27 @@ module Bitcoin
|
|
97
101
|
@h.on_alert Bitcoin::Protocol::Alert.parse(payload)
|
98
102
|
end
|
99
103
|
|
104
|
+
# https://en.bitcoin.it/wiki/BIP_0035
|
105
|
+
def handle_mempool_request(payload)
|
106
|
+
return unless @version[:version] >= 60002 # Protocol version >= 60002
|
107
|
+
return unless (@version[:services] & Bitcoin::Protocol::Version::NODE_NETWORK) == 1 # NODE_NETWORK bit set in Services
|
108
|
+
@h.on_mempool if @h.respond_to?(:on_mempool)
|
109
|
+
end
|
110
|
+
|
111
|
+
def handle_notfound_reply(payload)
|
112
|
+
return unless @h.respond_to?(:on_notfound)
|
113
|
+
count, payload = Protocol.unpack_var_int(payload)
|
114
|
+
payload.each_byte.each_slice(36){|i|
|
115
|
+
hash = i[4..-1].reverse.pack("C32")
|
116
|
+
case i[0]
|
117
|
+
when 1; @h.on_notfound(:tx, hash)
|
118
|
+
when 2; @h.on_notfound(:block, hash)
|
119
|
+
else
|
120
|
+
p ['handle_notfound_reply error', i, hash]
|
121
|
+
end
|
122
|
+
}
|
123
|
+
end
|
124
|
+
|
100
125
|
def parse(buf)
|
101
126
|
@buf += buf
|
102
127
|
while parse_buffer; end
|
@@ -108,20 +133,20 @@ module Bitcoin
|
|
108
133
|
head_size = 24
|
109
134
|
return false if @buf.size <= head_size
|
110
135
|
|
111
|
-
magic, cmd, length, checksum = @buf.unpack("
|
136
|
+
magic, cmd, length, checksum = @buf.unpack("a4A12Va4")
|
112
137
|
payload = @buf[head_size...head_size+length]
|
113
138
|
|
114
139
|
unless magic == head_magic
|
115
|
-
|
140
|
+
handle_stream_error(:close, "head_magic not found")
|
116
141
|
@buf = ''
|
117
142
|
else
|
118
143
|
|
119
144
|
if Digest::SHA256.digest(Digest::SHA256.digest( payload ))[0...4] != checksum
|
120
145
|
if (length < 50000) && (payload.size < length)
|
121
146
|
size_info = [payload.size, length].join('/')
|
122
|
-
|
147
|
+
handle_stream_error(:debug, "chunked packet stream (#{size_info})")
|
123
148
|
else
|
124
|
-
|
149
|
+
handle_stream_error(:close, "checksum mismatch")
|
125
150
|
end
|
126
151
|
return
|
127
152
|
end
|
@@ -134,7 +159,7 @@ module Bitcoin
|
|
134
159
|
@buf[0] != nil
|
135
160
|
end
|
136
161
|
|
137
|
-
def
|
162
|
+
def handle_stream_error(type, msg)
|
138
163
|
case type
|
139
164
|
when :close
|
140
165
|
log.debug {"closing packet stream (#{msg})"}
|
data/lib/bitcoin/protocol/tx.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
require 'bitcoin/script'
|
2
4
|
|
3
5
|
module Bitcoin
|
@@ -39,12 +41,12 @@ module Bitcoin
|
|
39
41
|
# create tx from raw binary +data+
|
40
42
|
def initialize(data=nil)
|
41
43
|
@ver, @lock_time, @in, @out = 1, 0, [], []
|
42
|
-
|
44
|
+
parse_data_from_io(data) if data
|
43
45
|
end
|
44
46
|
|
45
47
|
# generate the tx hash for given +payload+ in hex format
|
46
48
|
def hash_from_payload(payload)
|
47
|
-
Digest::SHA256.digest(Digest::SHA256.digest( payload )).
|
49
|
+
Digest::SHA256.digest(Digest::SHA256.digest( payload )).reverse_hth
|
48
50
|
end
|
49
51
|
alias generate_hash hash_from_payload
|
50
52
|
|
@@ -55,49 +57,49 @@ module Bitcoin
|
|
55
57
|
def add_out(output); (@out ||= []) << output; end
|
56
58
|
|
57
59
|
# parse raw binary data
|
58
|
-
def
|
59
|
-
|
60
|
-
|
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
|
-
}
|
60
|
+
def parse_data_from_io(data)
|
61
|
+
buf = data.is_a?(String) ? StringIO.new(data) : data
|
62
|
+
payload_start = buf.pos
|
70
63
|
|
71
|
-
|
72
|
-
idx += data[idx..-1].bytesize-tmp.bytesize
|
64
|
+
@ver = buf.read(4).unpack("V")[0]
|
73
65
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
66
|
+
in_size = Protocol.unpack_var_int_from_io(buf)
|
67
|
+
@in = []
|
68
|
+
in_size.times{ @in << TxIn.from_io(buf) }
|
69
|
+
|
70
|
+
out_size = Protocol.unpack_var_int_from_io(buf)
|
71
|
+
@out = []
|
72
|
+
out_size.times{ @out << TxOut.from_io(buf) }
|
79
73
|
|
80
|
-
@lock_time =
|
74
|
+
@lock_time = buf.read(4).unpack("V")[0]
|
81
75
|
|
82
|
-
|
76
|
+
payload_end = buf.pos;
|
77
|
+
buf.seek(payload_start)
|
78
|
+
@payload = buf.read( payload_end-payload_start )
|
83
79
|
@hash = hash_from_payload(@payload)
|
84
80
|
|
85
|
-
if
|
86
|
-
true
|
81
|
+
if buf.eof?
|
82
|
+
true
|
87
83
|
else
|
88
|
-
data
|
84
|
+
data.is_a?(StringIO) ? buf : buf.read
|
89
85
|
end
|
90
86
|
end
|
91
87
|
|
88
|
+
alias :parse_data :parse_data_from_io
|
89
|
+
|
92
90
|
# output transaction in raw binary format
|
93
91
|
def to_payload
|
94
|
-
pin
|
95
|
-
|
92
|
+
pin = ""
|
93
|
+
@in.each{|input| pin << input.to_payload }
|
94
|
+
pout = ""
|
95
|
+
@out.each{|output| pout << output.to_payload }
|
96
96
|
|
97
|
-
|
98
|
-
[[@ver].pack("I"), in_size, pin, out_size, pout, [@lock_time].pack("I")].join
|
97
|
+
[@ver].pack("V") << Protocol.pack_var_int(@in.size) << pin << Protocol.pack_var_int(@out.size) << pout << [@lock_time].pack("V")
|
99
98
|
end
|
100
99
|
|
100
|
+
|
101
|
+
SIGHASH_TYPE = { all: 1, none: 2, single: 3, anyonecanpay: 128 }
|
102
|
+
|
101
103
|
# generate a signature hash for input +input_idx+.
|
102
104
|
# either pass the +outpoint_tx+ or the +script_pubkey+ directly.
|
103
105
|
def signature_hash_for_input(input_idx, outpoint_tx, script_pubkey=nil, hash_type=nil, drop_sigs=nil, script=nil)
|
@@ -105,46 +107,58 @@ module Bitcoin
|
|
105
107
|
# http://code.google.com/p/bitcoinj/source/browse/trunk/src/com/google/bitcoin/core/Script.java#318
|
106
108
|
# https://en.bitcoin.it/wiki/OP_CHECKSIG#How_it_works
|
107
109
|
# https://github.com/bitcoin/bitcoin/blob/c2e8c8acd8ae0c94c70b59f55169841ad195bb99/src/script.cpp#L1058
|
110
|
+
# https://en.bitcoin.it/wiki/OP_CHECKSIG
|
108
111
|
|
109
|
-
|
112
|
+
return "\x01".ljust(32, "\x00") if input_idx >= @in.size # ERROR: SignatureHash() : input_idx=%d out of range
|
113
|
+
|
114
|
+
hash_type ||= SIGHASH_TYPE[:all]
|
110
115
|
|
111
116
|
pin = @in.map.with_index{|input,idx|
|
112
117
|
if idx == input_idx
|
113
118
|
script_pubkey ||= outpoint_tx.out[ input.prev_out_index ].pk_script
|
114
|
-
script_pubkey =
|
115
|
-
script_pubkey = Bitcoin::Script.drop_signatures(script_pubkey, drop_sigs) if drop_sigs # array of signature to drop
|
119
|
+
script_pubkey = script if script # force binary aa script
|
120
|
+
script_pubkey = Bitcoin::Script.drop_signatures(script_pubkey, drop_sigs) if drop_sigs # array of signature to drop (slow)
|
121
|
+
#p Bitcoin::Script.new(script_pubkey).to_string
|
116
122
|
input.to_payload(script_pubkey)
|
117
123
|
else
|
118
|
-
case hash_type
|
119
|
-
when
|
120
|
-
|
124
|
+
case (hash_type & 0x1f)
|
125
|
+
when SIGHASH_TYPE[:none]; input.to_payload("", "\x00\x00\x00\x00")
|
126
|
+
when SIGHASH_TYPE[:single]; input.to_payload("", "\x00\x00\x00\x00")
|
127
|
+
else; input.to_payload("")
|
121
128
|
end
|
122
129
|
end
|
123
|
-
}
|
130
|
+
}
|
124
131
|
|
125
|
-
pout = @out.map(&:to_payload)
|
132
|
+
pout = @out.map(&:to_payload)
|
133
|
+
in_size, out_size = Protocol.pack_var_int(@in.size), Protocol.pack_var_int(@out.size)
|
126
134
|
|
127
|
-
case hash_type
|
128
|
-
when
|
135
|
+
case (hash_type & 0x1f)
|
136
|
+
when SIGHASH_TYPE[:none]
|
129
137
|
pout = ""
|
130
|
-
|
131
|
-
|
132
|
-
|
138
|
+
out_size = Protocol.pack_var_int(0)
|
139
|
+
when SIGHASH_TYPE[:single]
|
140
|
+
return "\x01".ljust(32, "\x00") if input_idx >= @out.size # ERROR: SignatureHash() : input_idx=%d out of range (SIGHASH_SINGLE)
|
141
|
+
pout = @out[0...(input_idx+1)].map.with_index{|out,idx| (idx==input_idx) ? out.to_payload : out.to_null_payload }.join
|
142
|
+
out_size = Protocol.pack_var_int(input_idx+1)
|
133
143
|
end
|
134
144
|
|
135
|
-
|
145
|
+
if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
|
146
|
+
in_size, pin = Protocol.pack_var_int(1), [ pin[input_idx] ]
|
147
|
+
end
|
148
|
+
|
149
|
+
buf = [ [@ver].pack("V"), in_size, pin, out_size, pout, [@lock_time, hash_type].pack("VV") ].join
|
136
150
|
Digest::SHA256.digest( Digest::SHA256.digest( buf ) )
|
137
151
|
end
|
138
152
|
|
139
153
|
# verify input signature +in_idx+ against the corresponding
|
140
154
|
# output in +outpoint_tx+
|
141
|
-
def verify_input_signature(in_idx, outpoint_tx)
|
155
|
+
def verify_input_signature(in_idx, outpoint_tx, block_timestamp=Time.now.to_i)
|
142
156
|
outpoint_idx = @in[in_idx].prev_out_index
|
143
157
|
script_sig = @in[in_idx].script_sig
|
144
158
|
script_pubkey = outpoint_tx.out[outpoint_idx].pk_script
|
145
159
|
script = script_sig + script_pubkey
|
146
160
|
|
147
|
-
Bitcoin::Script.new(script).run do |pubkey,sig,hash_type,drop_sigs,script|
|
161
|
+
Bitcoin::Script.new(script).run(block_timestamp) do |pubkey,sig,hash_type,drop_sigs,script|
|
148
162
|
# this IS the checksig callback, must return true/false
|
149
163
|
hash = signature_hash_for_input(in_idx, outpoint_tx, nil, hash_type, drop_sigs, script)
|
150
164
|
#hash = signature_hash_for_input(in_idx, nil, script_pubkey, hash_type, drop_sigs, script)
|
@@ -153,20 +167,21 @@ module Bitcoin
|
|
153
167
|
end
|
154
168
|
|
155
169
|
# convert to ruby hash (see also #from_hash)
|
156
|
-
def to_hash
|
170
|
+
def to_hash(options = {})
|
157
171
|
@hash ||= hash_from_payload(to_payload)
|
158
|
-
{
|
172
|
+
h = {
|
159
173
|
'hash' => @hash, 'ver' => @ver,
|
160
174
|
'vin_sz' => @in.size, 'vout_sz' => @out.size,
|
161
175
|
'lock_time' => @lock_time, 'size' => (@payload ||= to_payload).bytesize,
|
162
|
-
'in' => @in.map(
|
163
|
-
'out' => @out.map(
|
176
|
+
'in' => @in.map{|i| i.to_hash(options) },
|
177
|
+
'out' => @out.map{|o| o.to_hash(options) }
|
164
178
|
}
|
179
|
+
h
|
165
180
|
end
|
166
181
|
|
167
182
|
# generates rawblock json as seen in the block explorer.
|
168
183
|
def to_json(options = {:space => ''}, *a)
|
169
|
-
JSON.pretty_generate( to_hash, options )
|
184
|
+
JSON.pretty_generate( to_hash(options), options )
|
170
185
|
end
|
171
186
|
|
172
187
|
# write json representation to a file
|
@@ -199,7 +214,45 @@ module Bitcoin
|
|
199
214
|
|
200
215
|
# read json block from a file
|
201
216
|
def self.from_json_file(path); from_json( Bitcoin::Protocol.read_binary_file(path) ); end
|
202
|
-
end
|
203
217
|
|
218
|
+
def validator(store, block = nil)
|
219
|
+
@validator ||= Bitcoin::Validation::Tx.new(self, store, block)
|
220
|
+
end
|
221
|
+
|
222
|
+
def minimum_relay_fee; calculate_minimum_fee(1_000, true, :relay); end
|
223
|
+
def minimum_block_fee; calculate_minimum_fee(1_000, true, :block); end
|
224
|
+
|
225
|
+
def calculate_minimum_fee(block_size=1, allow_free=true, mode=:block)
|
226
|
+
base_fee = (mode == :relay) ? Bitcoin.network[:min_relay_tx_fee] : Bitcoin.network[:min_tx_fee]
|
227
|
+
tx_size = to_payload.bytesize
|
228
|
+
new_block_size = block_size + tx_size
|
229
|
+
min_fee = (1 + tx_size / 1_000) * base_fee
|
230
|
+
|
231
|
+
if allow_free
|
232
|
+
if block_size == 1
|
233
|
+
min_fee = 0 if tx_size < 10_000
|
234
|
+
else
|
235
|
+
min_fee = 0 if new_block_size < 27_000
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
if min_fee < base_fee
|
240
|
+
outputs.each{|output| (min_fee = base_fee; break) if output.value < Bitcoin::CENT }
|
241
|
+
end
|
242
|
+
|
243
|
+
if block_size != 1 && new_block_size >= (Bitcoin::MAX_BLOCK_SIZE_GEN/2)
|
244
|
+
#return Bitcoin::network[:max_money] if new_block_size >= Bitcoin::MAX_BLOCK_SIZE_GEN
|
245
|
+
min_fee *= Bitcoin::MAX_BLOCK_SIZE_GEN / (Bitcoin::MAX_BLOCK_SIZE_GEN - new_block_size)
|
246
|
+
end
|
247
|
+
|
248
|
+
min_fee = Bitcoin::network[:max_money] unless min_fee.between?(0, Bitcoin::network[:max_money])
|
249
|
+
min_fee
|
250
|
+
end
|
251
|
+
|
252
|
+
def is_coinbase?
|
253
|
+
inputs.size == 1 and inputs.first.coinbase?
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
204
257
|
end
|
205
258
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: ascii-8bit
|
2
|
+
|
1
3
|
module Bitcoin
|
2
4
|
module Protocol
|
3
5
|
|
@@ -12,10 +14,19 @@ module Bitcoin
|
|
12
14
|
# script_sig input Script (signature)
|
13
15
|
attr_accessor :script_sig, :script_sig_length
|
14
16
|
|
17
|
+
# signature hash and the address of the key that needs to sign it
|
18
|
+
# (used when dealing with unsigned or partly signed tx)
|
19
|
+
attr_accessor :sig_hash, :sig_address
|
20
|
+
|
21
|
+
alias :script :script_sig
|
22
|
+
alias :script_length :script_sig_length
|
23
|
+
|
15
24
|
# sequence
|
16
25
|
attr_accessor :sequence
|
17
26
|
|
18
27
|
DEFAULT_SEQUENCE = "\xff\xff\xff\xff"
|
28
|
+
NULL_HASH = "\x00"*32
|
29
|
+
COINBASE_INDEX = 0xffffffff
|
19
30
|
|
20
31
|
def initialize *args
|
21
32
|
@prev_out, @prev_out_index, @script_sig_length,
|
@@ -33,32 +44,34 @@ module Bitcoin
|
|
33
44
|
|
34
45
|
# parse raw binary data for transaction input
|
35
46
|
def parse_data(data)
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
47
|
+
buf = data.is_a?(String) ? StringIO.new(data) : data
|
48
|
+
parse_data_from_io(buf)
|
49
|
+
buf.pos
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.from_io(buf)
|
53
|
+
txin = new; txin.parse_data_from_io(buf); txin
|
43
54
|
end
|
44
55
|
|
45
|
-
|
56
|
+
def parse_data_from_io(buf)
|
57
|
+
@prev_out, @prev_out_index = buf.read(36).unpack("a32V")
|
58
|
+
@script_sig_length = Protocol.unpack_var_int_from_io(buf)
|
59
|
+
@script_sig = buf.read(@script_sig_length)
|
60
|
+
@sequence = buf.read(4)
|
61
|
+
end
|
46
62
|
|
47
63
|
def to_payload(script=@script_sig, sequence=@sequence)
|
48
|
-
|
49
|
-
buf << Protocol.pack_var_int(script.bytesize)
|
50
|
-
buf << script if script.bytesize > 0
|
51
|
-
buf << (sequence || DEFAULT_SEQUENCE)
|
64
|
+
[@prev_out, @prev_out_index].pack("a32V") << Protocol.pack_var_int(script.bytesize) << script << (sequence || DEFAULT_SEQUENCE)
|
52
65
|
end
|
53
66
|
|
54
|
-
def to_hash
|
55
|
-
t = { 'prev_out' => { 'hash' => @prev_out.
|
67
|
+
def to_hash(options = {})
|
68
|
+
t = { 'prev_out' => { 'hash' => @prev_out.reverse_hth, 'n' => @prev_out_index } }
|
56
69
|
if coinbase?
|
57
70
|
t['coinbase'] = @script_sig.unpack("H*")[0]
|
58
71
|
else # coinbase tx
|
59
72
|
t['scriptSig'] = Bitcoin::Script.new(@script_sig).to_string
|
60
|
-
t['sequence'] = @sequence.unpack("I")[0] unless @sequence == "\xff\xff\xff\xff"
|
61
73
|
end
|
74
|
+
t['sequence'] = @sequence.unpack("V")[0] unless @sequence == "\xff\xff\xff\xff"
|
62
75
|
t
|
63
76
|
end
|
64
77
|
|
@@ -66,22 +79,25 @@ module Bitcoin
|
|
66
79
|
txin = TxIn.new([ input['prev_out']['hash'] ].pack('H*').reverse, input['prev_out']['n'])
|
67
80
|
if input['coinbase']
|
68
81
|
txin.script_sig = [ input['coinbase'] ].pack("H*")
|
69
|
-
txin.sequence = "\xff\xff\xff\xff"
|
70
82
|
else
|
71
83
|
txin.script_sig = Script.binary_from_string(input['scriptSig'])
|
72
|
-
txin.sequence = [ input['sequence'] || 0xffffffff ].pack("I")
|
73
84
|
end
|
85
|
+
txin.sequence = [ input['sequence'] || 0xffffffff ].pack("V")
|
74
86
|
txin
|
75
87
|
end
|
76
88
|
|
89
|
+
def self.from_hex_hash(hash, index)
|
90
|
+
TxIn.new([hash].pack("H*").reverse, index, 0)
|
91
|
+
end
|
92
|
+
|
77
93
|
# previous output in hex
|
78
94
|
def previous_output
|
79
|
-
@prev_out.
|
95
|
+
@prev_out.reverse_hth
|
80
96
|
end
|
81
97
|
|
82
98
|
# check if input is coinbase
|
83
99
|
def coinbase?
|
84
|
-
(@prev_out_index ==
|
100
|
+
(@prev_out_index == COINBASE_INDEX) && (@prev_out == NULL_HASH)
|
85
101
|
end
|
86
102
|
|
87
103
|
# set script_sig and script_sig_length
|
@@ -91,6 +107,10 @@ module Bitcoin
|
|
91
107
|
end
|
92
108
|
alias :script= :script_sig=
|
93
109
|
|
110
|
+
def add_signature_pubkey_script(sig, pubkey_hex)
|
111
|
+
self.script = Bitcoin::Script.to_signature_pubkey_script(sig, [pubkey_hex].pack("H*"))
|
112
|
+
end
|
113
|
+
|
94
114
|
end
|
95
115
|
|
96
116
|
end
|