bitcoin-ruby 0.0.1 → 0.0.2

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.
Files changed (136) hide show
  1. data/.gitignore +4 -1
  2. data/Gemfile +21 -0
  3. data/README.rdoc +85 -25
  4. data/Rakefile +7 -3
  5. data/bin/bitcoin_node +39 -42
  6. data/bin/bitcoin_shell +1 -0
  7. data/bin/bitcoin_wallet +129 -53
  8. data/bitcoin-ruby.gemspec +4 -7
  9. data/concept-examples/blockchain-pow.rb +1 -1
  10. data/doc/CONFIG.rdoc +5 -5
  11. data/doc/EXAMPLES.rdoc +9 -5
  12. data/doc/NAMECOIN.rdoc +34 -0
  13. data/doc/NODE.rdoc +147 -10
  14. data/examples/balance.rb +10 -4
  15. data/examples/bbe_verify_tx.rb +7 -2
  16. data/examples/forwarder.rb +73 -0
  17. data/examples/generate_tx.rb +34 -0
  18. data/examples/simple_network_monitor_and_util.rb +187 -0
  19. data/examples/verify_tx.rb +1 -1
  20. data/lib/bitcoin.rb +308 -18
  21. data/lib/bitcoin/builder.rb +62 -36
  22. data/lib/bitcoin/config.rb +2 -0
  23. data/lib/bitcoin/connection.rb +11 -8
  24. data/lib/bitcoin/electrum/mnemonic.rb +162 -0
  25. data/lib/bitcoin/ffi/openssl.rb +187 -21
  26. data/lib/bitcoin/gui/addr_view.rb +2 -0
  27. data/lib/bitcoin/gui/conn_view.rb +2 -0
  28. data/lib/bitcoin/gui/connection.rb +2 -0
  29. data/lib/bitcoin/gui/em_gtk.rb +2 -0
  30. data/lib/bitcoin/gui/gui.rb +2 -0
  31. data/lib/bitcoin/gui/helpers.rb +2 -0
  32. data/lib/bitcoin/gui/tree_view.rb +2 -0
  33. data/lib/bitcoin/gui/tx_view.rb +2 -0
  34. data/lib/bitcoin/key.rb +77 -11
  35. data/lib/bitcoin/litecoin.rb +81 -0
  36. data/lib/bitcoin/logger.rb +20 -1
  37. data/lib/bitcoin/namecoin.rb +279 -0
  38. data/lib/bitcoin/network/command_client.rb +7 -6
  39. data/lib/bitcoin/network/command_handler.rb +229 -43
  40. data/lib/bitcoin/network/connection_handler.rb +182 -70
  41. data/lib/bitcoin/network/node.rb +231 -106
  42. data/lib/bitcoin/protocol.rb +44 -23
  43. data/lib/bitcoin/protocol/address.rb +5 -3
  44. data/lib/bitcoin/protocol/alert.rb +3 -4
  45. data/lib/bitcoin/protocol/aux_pow.rb +123 -0
  46. data/lib/bitcoin/protocol/block.rb +98 -18
  47. data/lib/bitcoin/protocol/handler.rb +6 -5
  48. data/lib/bitcoin/protocol/parser.rb +44 -19
  49. data/lib/bitcoin/protocol/tx.rb +105 -52
  50. data/lib/bitcoin/protocol/txin.rb +39 -19
  51. data/lib/bitcoin/protocol/txout.rb +28 -13
  52. data/lib/bitcoin/protocol/version.rb +16 -7
  53. data/lib/bitcoin/script.rb +579 -122
  54. data/lib/bitcoin/storage/{dummy.rb → dummy/dummy_store.rb} +8 -14
  55. data/lib/bitcoin/storage/models.rb +20 -7
  56. data/lib/bitcoin/storage/{sequel_store/sequel_migrations.rb → sequel/migrations.rb} +22 -7
  57. data/lib/bitcoin/storage/sequel/migrations/001_base_schema.rb +52 -0
  58. data/lib/bitcoin/storage/sequel/migrations/002_tx.rb +50 -0
  59. data/lib/bitcoin/storage/sequel/migrations/003_change_txin_script_sig_to_blob.rb +18 -0
  60. data/lib/bitcoin/storage/sequel/sequel_store.rb +436 -0
  61. data/lib/bitcoin/storage/storage.rb +233 -28
  62. data/lib/bitcoin/storage/utxo/migrations/001_base_schema.rb +52 -0
  63. data/lib/bitcoin/storage/utxo/migrations/002_utxo.rb +18 -0
  64. data/lib/bitcoin/storage/utxo/utxo_store.rb +361 -0
  65. data/lib/bitcoin/validation.rb +369 -0
  66. data/lib/bitcoin/version.rb +1 -1
  67. data/lib/bitcoin/wallet/coinselector.rb +3 -0
  68. data/lib/bitcoin/wallet/keygenerator.rb +3 -1
  69. data/lib/bitcoin/wallet/keystore.rb +6 -2
  70. data/lib/bitcoin/wallet/txdp.rb +6 -4
  71. data/lib/bitcoin/wallet/wallet.rb +54 -16
  72. data/spec/bitcoin/bitcoin_spec.rb +48 -3
  73. data/spec/bitcoin/builder_spec.rb +40 -17
  74. data/spec/bitcoin/fixtures/000000000000056b1a3d84a1e2b33cde8915a4b61c0cae14fca6d3e1490b4f98.json +3697 -0
  75. data/spec/bitcoin/fixtures/03d7e1fa4d5fefa169431f24f7798552861b255cd55d377066fedcd088fb0e99.json +23 -0
  76. data/spec/bitcoin/fixtures/0961c660358478829505e16a1f028757e54b5bbf9758341a7546573738f31429.json +24 -0
  77. data/spec/bitcoin/fixtures/0f24294a1d23efbb49c1765cf443fba7930702752aba6d765870082fe4f13cae.json +37 -0
  78. data/spec/bitcoin/fixtures/315ac7d4c26d69668129cc352851d9389b4a6868f1509c6c8b66bead11e2619f.json +31 -0
  79. data/spec/bitcoin/fixtures/35e2001b428891fefa0bfb73167c7360669d3cbd7b3aa78e7cad125ddfc51131.json +27 -0
  80. data/spec/bitcoin/fixtures/3a17dace09ffb919ed627a93f1873220f4c975c1248558b18d16bce25d38c4b7.json +72 -0
  81. data/spec/bitcoin/fixtures/3e58b7eed0fdb599019af08578effea25c8666bbe8e200845453cacce6314477.json +27 -0
  82. data/spec/bitcoin/fixtures/514c46f0b61714092f15c8dfcb576c9f79b3f959989b98de3944b19d98832b58.json +24 -0
  83. data/spec/bitcoin/fixtures/51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e.json +30 -0
  84. data/spec/bitcoin/fixtures/69216b8aaa35b76d6613e5f527f4858640d986e1046238583bdad79b35e938dc.json +28 -0
  85. data/spec/bitcoin/fixtures/7208e5edf525f04e705fb3390194e316205b8f995c8c9fcd8c6093abe04fa27d.json +27 -0
  86. data/spec/bitcoin/fixtures/761d8c5210fdfd505f6dff38f740ae3728eb93d7d0971fb433f685d40a4c04f6.json +27 -0
  87. data/spec/bitcoin/fixtures/aea682d68a3ea5e3583e088dcbd699a5d44d4b083f02ad0aaf2598fe1fa4dfd4.json +27 -0
  88. data/spec/bitcoin/fixtures/bd1715f1abfdc62bea3f605bdb461b3ba1f2cca6ec0d73a18a548b7717ca8531.json +34 -0
  89. data/spec/bitcoin/fixtures/block-testnet-0000000000ac85bb2530a05a4214a387e6be02b22d3348abc5e7a5d9c4ce8dab.bin +0 -0
  90. data/spec/bitcoin/fixtures/cd874fa8cb0e2ec2d385735d5e1fd482c4fe648533efb4c50ee53bda58e15ae2.json +24 -0
  91. data/spec/bitcoin/fixtures/ce5fad9b4ef094d8f4937b0707edaf0a6e6ceeaf67d5edbfd51f660eac8f398b.json +41 -0
  92. data/spec/bitcoin/fixtures/f003f0c1193019db2497a675fd05d9f2edddf9b67c59e677c48d3dbd4ed5f00b.json +23 -0
  93. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.bin +0 -0
  94. data/spec/bitcoin/fixtures/freicoin-block-000000005d231b285e63af83edae2d8f5e50e70d396468643092b9239fd3be3c.json +43 -0
  95. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.bin +0 -0
  96. data/spec/bitcoin/fixtures/freicoin-genesis-block-000000005b1e3d23ecfd2dd4a6e1a35238aa0392c0a8528c40df52376d7efe2c.json +67 -0
  97. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.bin +0 -0
  98. data/spec/bitcoin/fixtures/litecoin-block-80ca095ed10b02e53d769eb6eaf92cd04e9e0759e5be4a8477b42911ba49c78f.json +39 -0
  99. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.bin +0 -0
  100. data/spec/bitcoin/fixtures/litecoin-genesis-block-12a765e31ffd4059bada1e25190f6e98c99d9714d334efa41a195a7e7e04bfe2.json +39 -0
  101. data/spec/bitcoin/fixtures/rawblock-auxpow.bin +0 -0
  102. data/spec/bitcoin/fixtures/tx-313897799b1e37e9ecae15010e56156dddde4e683c96b0e713af95272c38aee0.json +30 -0
  103. data/spec/bitcoin/fixtures/tx-3da75972766f0ad13319b0b461fd16823a731e44f6e9de4eb3c52d6a6fb6c8ae.json +23 -0
  104. data/spec/bitcoin/fixtures/tx-44b833074e671120ba33106877b49e86ece510824b9af477a3853972bcd8d06a.json +30 -0
  105. data/spec/bitcoin/fixtures/tx-d3d77d63709e47d9ef58f0b557800115a6b676c6a423012fbb96f45d8fcef830.json +28 -0
  106. data/spec/bitcoin/key_spec.rb +128 -3
  107. data/spec/bitcoin/namecoin_spec.rb +182 -0
  108. data/spec/bitcoin/network_spec.rb +5 -3
  109. data/spec/bitcoin/node/command_api_spec.rb +376 -0
  110. data/spec/bitcoin/protocol/addr_spec.rb +2 -0
  111. data/spec/bitcoin/protocol/alert_spec.rb +2 -0
  112. data/spec/bitcoin/protocol/aux_pow_spec.rb +44 -0
  113. data/spec/bitcoin/protocol/block_spec.rb +134 -39
  114. data/spec/bitcoin/protocol/getblocks_spec.rb +32 -0
  115. data/spec/bitcoin/protocol/inv_spec.rb +10 -8
  116. data/spec/bitcoin/protocol/notfound_spec.rb +31 -0
  117. data/spec/bitcoin/protocol/ping_spec.rb +2 -0
  118. data/spec/bitcoin/protocol/tx_spec.rb +83 -17
  119. data/spec/bitcoin/protocol/version_spec.rb +7 -5
  120. data/spec/bitcoin/script/opcodes_spec.rb +412 -133
  121. data/spec/bitcoin/script/script_spec.rb +112 -13
  122. data/spec/bitcoin/spec_helper.rb +68 -0
  123. data/spec/bitcoin/storage/reorg_spec.rb +199 -0
  124. data/spec/bitcoin/storage/storage_spec.rb +337 -0
  125. data/spec/bitcoin/storage/validation_spec.rb +261 -0
  126. data/spec/bitcoin/wallet/coinselector_spec.rb +10 -7
  127. data/spec/bitcoin/wallet/keygenerator_spec.rb +2 -0
  128. data/spec/bitcoin/wallet/keystore_spec.rb +2 -0
  129. data/spec/bitcoin/wallet/txdp_spec.rb +2 -0
  130. data/spec/bitcoin/wallet/wallet_spec.rb +91 -58
  131. metadata +105 -51
  132. data/lib/bitcoin/storage/sequel.rb +0 -335
  133. data/spec/bitcoin/fixtures/0d0affb5964abe804ffe85e53f1dbb9f29e406aa3046e2db04fba240e63c7fdd.json +0 -27
  134. data/spec/bitcoin/fixtures/477fff140b363ec2cc51f3a65c0c58eda38f4d41f04a295bbd62babf25e4c590.json +0 -27
  135. data/spec/bitcoin/reorg_spec.rb +0 -129
  136. 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(hash)]
8
+ p ['inv transaction', hash.hth]
7
9
  end
8
10
 
9
11
  def on_inv_block(hash)
10
- p ['inv block', hth(hash)]
12
+ p ['inv block', hash.hth]
11
13
  end
12
14
 
13
15
  def on_get_transaction(hash)
14
- p ['get transaction', hth(hash)]
16
+ p ['get transaction', hash.hth]
15
17
  end
16
18
 
17
19
  def on_get_block(hash)
18
- p ['get block', hth(hash)]
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
- count, payload = Protocol.unpack_var_int(payload)
56
- idx = 0
57
- headers = count.times.map{ Block.new(payload[idx..idx+=81]) }
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('a4a*')
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 = Protocol.hth(i.reverse.pack("C32")) }
66
- stop_hash = Protocol.hth(payload[0..32].reverse)
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 'verack'; @h.on_handshake_complete # nop
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 ['unkown-packet', command, payload]
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("a4A12Ia4")
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
- handle_error(:close, "head_magic not found")
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
- handle_error(:debug, "chunked packet stream (#{size_info})")
147
+ handle_stream_error(:debug, "chunked packet stream (#{size_info})")
123
148
  else
124
- handle_error(:close, "checksum mismatch")
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 handle_error(type, msg)
162
+ def handle_stream_error(type, msg)
138
163
  case type
139
164
  when :close
140
165
  log.debug {"closing packet stream (#{msg})"}
@@ -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
- parse_data(data) if data
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 )).reverse.unpack("H*")[0]
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 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
- }
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
- out_size, tmp = Protocol.unpack_var_int(data[idx..-1])
72
- idx += data[idx..-1].bytesize-tmp.bytesize
64
+ @ver = buf.read(4).unpack("V")[0]
73
65
 
74
- @out = (0...out_size).map{
75
- txout = TxOut.new
76
- idx += txout.parse_data(data[idx..-1])
77
- txout
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 = data[idx...idx+=4].unpack("I")[0]
74
+ @lock_time = buf.read(4).unpack("V")[0]
81
75
 
82
- @payload = data[0...idx]
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 data[idx] == nil
86
- true # reached the end.
81
+ if buf.eof?
82
+ true
87
83
  else
88
- data[idx..-1] # rest of buffer.
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 = @in.map(&:to_payload).join
95
- pout = @out.map(&:to_payload).join
92
+ pin = ""
93
+ @in.each{|input| pin << input.to_payload }
94
+ pout = ""
95
+ @out.each{|output| pout << output.to_payload }
96
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
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
- hash_type ||= 1 # 1: ALL, 2: NONE, 3: SINGLE
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 = 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
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 2; input.to_payload("", "\x00\x00\x00\x00")
120
- else; input.to_payload("")
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
- }.join
130
+ }
124
131
 
125
- pout = @out.map(&:to_payload).join
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 2
135
+ case (hash_type & 0x1f)
136
+ when SIGHASH_TYPE[:none]
129
137
  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)
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
- buf = [ [@ver].pack("I"), in_size, pin, out_size, pout, [@lock_time, hash_type].pack("II") ].join
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(&:to_hash),
163
- 'out' => @out.map(&:to_hash)
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
- 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
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
- alias :parse_payload :parse_data
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
- 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)
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.reverse.unpack("H*")[0], 'n' => @prev_out_index } }
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.reverse.unpack("H*")[0]
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 == 4294967295) && (@prev_out == "\x00"*32)
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