bitcoinrb 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +10 -2
  3. data/bitcoinrb.gemspec +4 -1
  4. data/lib/bitcoin/block.rb +8 -0
  5. data/lib/bitcoin/constants.rb +9 -1
  6. data/lib/bitcoin/ext_key.rb +7 -3
  7. data/lib/bitcoin/key.rb +5 -5
  8. data/lib/bitcoin/message/headers_parser.rb +3 -3
  9. data/lib/bitcoin/message/inventory.rb +2 -2
  10. data/lib/bitcoin/message/merkle_block.rb +2 -2
  11. data/lib/bitcoin/message/reject.rb +2 -2
  12. data/lib/bitcoin/mnemonic.rb +1 -0
  13. data/lib/bitcoin/network/connection.rb +6 -3
  14. data/lib/bitcoin/network/message_handler.rb +3 -3
  15. data/lib/bitcoin/network/peer.rb +12 -0
  16. data/lib/bitcoin/network/pool.rb +23 -10
  17. data/lib/bitcoin/node/cli.rb +12 -0
  18. data/lib/bitcoin/node/spv.rb +9 -2
  19. data/lib/bitcoin/opcodes.rb +9 -3
  20. data/lib/bitcoin/out_point.rb +10 -1
  21. data/lib/bitcoin/payments/output.pb.rb +20 -0
  22. data/lib/bitcoin/payments/payment.pb.rb +26 -0
  23. data/lib/bitcoin/payments/payment_ack.pb.rb +17 -0
  24. data/lib/bitcoin/payments/payment_details.pb.rb +24 -0
  25. data/lib/bitcoin/payments/payment_request.pb.rb +79 -0
  26. data/lib/bitcoin/payments/x509_certificates.pb.rb +18 -0
  27. data/lib/bitcoin/payments.rb +14 -0
  28. data/lib/bitcoin/rpc/request_handler.rb +25 -3
  29. data/lib/bitcoin/script/script.rb +36 -6
  30. data/lib/bitcoin/script/script_error.rb +4 -0
  31. data/lib/bitcoin/script/script_interpreter.rb +7 -2
  32. data/lib/bitcoin/secp256k1/ruby.rb +1 -2
  33. data/lib/bitcoin/store/spv_chain.rb +1 -1
  34. data/lib/bitcoin/tx.rb +11 -0
  35. data/lib/bitcoin/tx_in.rb +10 -1
  36. data/lib/bitcoin/tx_out.rb +9 -0
  37. data/lib/bitcoin/version.rb +1 -1
  38. data/lib/bitcoin/wallet/account.rb +2 -2
  39. data/lib/bitcoin.rb +10 -0
  40. metadata +28 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3d66a90af2b70a6494a6b38d0602a4442a0173e27294cec5b389149b77bc7360
4
- data.tar.gz: 1b830903242397fece09064f51a150b9716ef1d06409f597ca60c6ee76e80ebc
3
+ metadata.gz: 369e8ba42e13931c345e3ebf13023a0b45433d647ea42849028557bfb9036aad
4
+ data.tar.gz: 460030a26c3cd367706ebb374efda4ad6c7acc3994daa61fd261195076838079
5
5
  SHA512:
6
- metadata.gz: 397ecea38a38e72c3a66d6ca18461a41af2ecfa747eb71c9193c9c7a1629c96e49a8338348a2bd88f3be0e01184bb5148dd6e53e968c83dae8c3da4d2105e253
7
- data.tar.gz: bf3fca048c69f4939f1c4cae0da6c9be03e0650dd18f271d5cac045d4a30528ef3e3268e3290c6f03cadb6d2dbd06cd6ac0405e11e4400359085bfbd36d55f46
6
+ metadata.gz: c741a2415026b02e1760fcd47586fbb8df7219502fbf4223bc6d560b313e765bc916871a4ed87d98f343c48e25f72d9bf49910df17073490a2a96db63304ef43
7
+ data.tar.gz: 13b72574c4413556d9522bd3825ab818beadf956673f5a3c1ff0d027e736d126176825a7a04bdc4be3d333b96aedb9e9580339a8219937844bd5fd0b9b9d3ba2
data/README.md CHANGED
@@ -19,9 +19,11 @@ Bitcoinrb supports following feature:
19
19
 
20
20
  ## Requirements
21
21
 
22
- bitcoinrb requires a `leveldb` library.
22
+ ### use Node implementation
23
23
 
24
- ### install LevelDB
24
+ If you use node features, please install level DB as follows.
25
+
26
+ #### install LevelDB
25
27
 
26
28
  * for Ubuntu
27
29
 
@@ -31,6 +33,12 @@ bitcoinrb requires a `leveldb` library.
31
33
 
32
34
  $ brew install leveldb
33
35
 
36
+ and put `leveldb-ruby` in your Gemfile and run bundle install.
37
+
38
+ ```
39
+ gem leveldb-ruby
40
+ ```
41
+
34
42
  ## Installation
35
43
 
36
44
  Add this line to your application's Gemfile:
data/bitcoinrb.gemspec CHANGED
@@ -28,11 +28,14 @@ Gem::Specification.new do |spec|
28
28
  spec.add_runtime_dependency 'thor'
29
29
  spec.add_runtime_dependency 'ffi'
30
30
  spec.add_runtime_dependency 'leb128', '~> 1.0.0'
31
- spec.add_runtime_dependency 'leveldb-ruby'
32
31
  spec.add_runtime_dependency 'eventmachine_httpserver'
33
32
  spec.add_runtime_dependency 'rest-client'
34
33
  spec.add_runtime_dependency 'iniparse'
35
34
  spec.add_runtime_dependency 'siphash'
35
+ spec.add_runtime_dependency 'protobuf'
36
+
37
+ # for options
38
+ spec.add_development_dependency 'leveldb-ruby'
36
39
 
37
40
  spec.add_development_dependency 'bundler', '~> 1.11'
38
41
  spec.add_development_dependency 'rake', '~> 10.0'
data/lib/bitcoin/block.rb CHANGED
@@ -9,6 +9,14 @@ module Bitcoin
9
9
  @transactions = transactions
10
10
  end
11
11
 
12
+ def self.parse_from_payload(payload)
13
+ Bitcoin::Message::Block.parse_from_payload(payload).to_block
14
+ end
15
+
16
+ def hash
17
+ header.hash
18
+ end
19
+
12
20
  # calculate block weight
13
21
  def weight
14
22
  stripped_size * (WITNESS_SCALE_FACTOR - 1) + size
@@ -40,6 +40,7 @@ module Bitcoin
40
40
  SCRIPT_VERIFY_MINIMALIF = (1 << 13) # Segwit script only: Require the argument of OP_IF/NOTIF to be exactly 0x01 or empty vector
41
41
  SCRIPT_VERIFY_NULLFAIL = (1 << 14) # Signature(s) must be empty vector if an CHECK(MULTI)SIG operation failed
42
42
  SCRIPT_VERIFY_WITNESS_PUBKEYTYPE = (1 << 15) # Public keys in segregated witness scripts must be compressed
43
+ SCRIPT_VERIFY_CONST_SCRIPTCODE = (1 << 16) # Making OP_CODESEPARATOR and FindAndDelete fail any non-segwit scripts
43
44
 
44
45
  MANDATORY_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH
45
46
 
@@ -56,7 +57,10 @@ module Bitcoin
56
57
  SCRIPT_VERIFY_CHECKLOCKTIMEVERIFY,
57
58
  SCRIPT_VERIFY_CHECKSEQUENCEVERIFY,
58
59
  SCRIPT_VERIFY_LOW_S,
59
- SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
60
+ SCRIPT_VERIFY_WITNESS,
61
+ SCRIPT_VERIFY_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM,
62
+ SCRIPT_VERIFY_WITNESS_PUBKEYTYPE,
63
+ SCRIPT_VERIFY_CONST_SCRIPTCODE].inject(SCRIPT_VERIFY_NONE){|flags, f| flags |= f}
60
64
 
61
65
  # for script
62
66
 
@@ -156,6 +160,10 @@ module Bitcoin
156
160
  SCRIPT_ERR_WITNESS_UNEXPECTED = 75
157
161
  SCRIPT_ERR_WITNESS_PUBKEYTYPE = 76
158
162
 
163
+ # Constant scriptCode
164
+ SCRIPT_ERR_OP_CODESEPARATOR = 77
165
+ SCRIPT_ERR_SIG_FINDANDDELETE = 78
166
+
159
167
  SCRIPT_ERR_ERROR_COUNT = 80
160
168
 
161
169
  ERRCODES_MAP = Hash[*constants.grep(/^SCRIPT_ERR_/).map { |c| [const_get(c), c.to_s] }.flatten]
@@ -1,7 +1,7 @@
1
1
  module Bitcoin
2
2
 
3
3
  # Integers modulo the order of the curve(secp256k1)
4
- CURVE_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337
4
+ CURVE_ORDER = ECDSA::Group::Secp256k1.order
5
5
 
6
6
  # BIP32 Extended private key
7
7
  class ExtKey
@@ -87,7 +87,11 @@ module Bitcoin
87
87
  end
88
88
 
89
89
  # derive new key
90
- def derive(number)
90
+ # @param [Integer] number a child index
91
+ # @param [Boolean] harden whether hardened key or not. If true, 2^31 is added to +number+.
92
+ # @return [Bitcoin::ExtKey] derived new key.
93
+ def derive(number, harden = false)
94
+ number += 2**31 if harden
91
95
  new_key = ExtKey.new
92
96
  new_key.depth = depth + 1
93
97
  new_key.number = number
@@ -102,7 +106,7 @@ module Bitcoin
102
106
  raise 'invalid key' if left >= CURVE_ORDER
103
107
  child_priv = (left + key.priv_key.to_i(16)) % CURVE_ORDER
104
108
  raise 'invalid key ' if child_priv >= CURVE_ORDER
105
- new_key.key = Bitcoin::Key.new(priv_key: child_priv.to_s(16).rjust(64, '0'))
109
+ new_key.key = Bitcoin::Key.new(priv_key: child_priv.to_even_length_hex.rjust(64, '0'))
106
110
  new_key.chain_code = l[32..-1]
107
111
  new_key.ver = version
108
112
  new_key
data/lib/bitcoin/key.rb CHANGED
@@ -13,7 +13,7 @@ module Bitcoin
13
13
 
14
14
  MIN_PRIV_KEy_MOD_ORDER = 0x01
15
15
  # Order of secp256k1's generator minus 1.
16
- MAX_PRIV_KEY_MOD_ORDER = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364140
16
+ MAX_PRIV_KEY_MOD_ORDER = ECDSA::Group::Secp256k1.order - 1
17
17
 
18
18
  def initialize(priv_key: nil, pubkey: nil, compressed: true)
19
19
  @secp256k1_module = Bitcoin.secp_impl
@@ -94,17 +94,17 @@ module Bitcoin
94
94
 
95
95
  # get pay to pubkey hash address
96
96
  def to_p2pkh
97
- Bitcoin::Script.to_p2pkh(hash160).to_addr
97
+ Bitcoin::Script.to_p2pkh(hash160).addresses.first
98
98
  end
99
99
 
100
100
  # get pay to witness pubkey hash address
101
101
  def to_p2wpkh
102
- Bitcoin::Script.to_p2wpkh(hash160).to_addr
102
+ Bitcoin::Script.to_p2wpkh(hash160).addresses.first
103
103
  end
104
104
 
105
105
  # get p2wpkh address nested in p2sh.
106
106
  def to_nested_p2wpkh
107
- Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
107
+ Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.addresses.first
108
108
  end
109
109
 
110
110
  def compressed?
@@ -236,7 +236,7 @@ module Bitcoin
236
236
  r = sig_array[4...(len_r+4)].pack('C*').bth
237
237
  len_s = sig_array[len_r + 5]
238
238
  s = sig_array[(len_r + 6)...(len_r + 6 + len_s)].pack('C*').bth
239
- ECDSA::Format::SignatureDerString.encode(ECDSA::Signature.new(r.to_i(16), s.to_i(16)))
239
+ ECDSA::Signature.new(r.to_i(16), s.to_i(16)).to_der
240
240
  end
241
241
 
242
242
  def valid_pubkey?
@@ -10,13 +10,13 @@ module Bitcoin
10
10
  hashes = []
11
11
  buf = StringIO.new(payload)
12
12
  size.times do
13
- hashes << buf.read(32).reverse.bth
13
+ hashes << buf.read(32).bth
14
14
  end
15
- new(ver, hashes, buf.read(32).reverse.bth)
15
+ new(ver, hashes, buf.read(32).bth)
16
16
  end
17
17
 
18
18
  def to_payload
19
- [version].pack('V') << Bitcoin.pack_var_int(hashes.length) << hashes.map{|h|h.htb.reverse}.join << stop_hash.htb.reverse
19
+ [version].pack('V') << Bitcoin.pack_var_int(hashes.length) << hashes.map{|h|h.htb}.join << stop_hash.htb
20
20
  end
21
21
 
22
22
  end
@@ -27,12 +27,12 @@ module Bitcoin
27
27
  def self.parse_from_payload(payload)
28
28
  raise Error, 'invalid inventory size.' if payload.bytesize != 36
29
29
  identifier = payload[0..4].unpack('V').first
30
- hash = payload[4..-1].reverse.bth # internal byte order
30
+ hash = payload[4..-1].bth # internal byte order
31
31
  new(identifier, hash)
32
32
  end
33
33
 
34
34
  def to_payload
35
- [identifier].pack('V') << hash.htb.reverse
35
+ [identifier].pack('V') << hash.htb
36
36
  end
37
37
 
38
38
  def block?
@@ -23,7 +23,7 @@ module Bitcoin
23
23
  m.tx_count = buf.read(4).unpack('V').first
24
24
  hash_count = Bitcoin.unpack_var_int_from_io(buf)
25
25
  hash_count.times do
26
- m.hashes << buf.read(32).reverse.bth
26
+ m.hashes << buf.read(32).bth
27
27
  end
28
28
  flag_count = Bitcoin.unpack_var_int_from_io(buf)
29
29
  # A sequence of bits packed eight in a byte with the least significant bit first.
@@ -33,7 +33,7 @@ module Bitcoin
33
33
 
34
34
  def to_payload
35
35
  header.to_payload << [tx_count].pack('V') << Bitcoin.pack_var_int(hashes.size) <<
36
- hashes.map{|h|h.htb.reverse}.join << Bitcoin.pack_var_int(flags.htb.bytesize) << flags.htb
36
+ hashes.map(&:htb).join << Bitcoin.pack_var_int(flags.htb.bytesize) << flags.htb
37
37
  end
38
38
 
39
39
  end
@@ -32,12 +32,12 @@ module Bitcoin
32
32
  message, payload = Bitcoin.unpack_var_string(payload)
33
33
  code, payload = payload.unpack('Ca*')
34
34
  reason, payload = Bitcoin.unpack_var_string(payload)
35
- extra = ['tx', 'block'].include?(message) ? payload.reverse.bth : payload
35
+ extra = ['tx', 'block'].include?(message) ? payload.bth : payload
36
36
  new(message, code, reason, extra)
37
37
  end
38
38
 
39
39
  def to_payload
40
- e = ['tx', 'block'].include?(message) ? extra.htb.reverse : extra
40
+ e = ['tx', 'block'].include?(message) ? extra.htb : extra
41
41
  Bitcoin.pack_var_string(message) << [code].pack('C') << Bitcoin.pack_var_string(reason) << e
42
42
  end
43
43
 
@@ -52,6 +52,7 @@ module Bitcoin
52
52
  # @param [String] passphrase a passphrase which protects mnemonic. the default value is an empty string.
53
53
  # @return [String] seed
54
54
  def to_seed(mnemonic, passphrase: '')
55
+ to_entropy(mnemonic)
55
56
  OpenSSL::PKCS5.pbkdf2_hmac(mnemonic.join(' ').downcase,
56
57
  'mnemonic' + passphrase, 2048, 64, OpenSSL::Digest::SHA512.new).bth
57
58
  end
@@ -24,6 +24,7 @@ module Bitcoin
24
24
  @sendheaders = false
25
25
  @attr_accessor = 0
26
26
  @message = ''
27
+ self.pending_connect_timeout = 5.0
27
28
  end
28
29
 
29
30
  def post_init
@@ -56,6 +57,11 @@ module Bitcoin
56
57
  peer.handle_error(e)
57
58
  end
58
59
 
60
+ def unbind
61
+ logger.info "unbind. #{addr}"
62
+ peer.unbind
63
+ end
64
+
59
65
  private
60
66
 
61
67
  # start handshake
@@ -63,9 +69,6 @@ module Bitcoin
63
69
  logger.info "begin handshake with #{addr}"
64
70
  send_message(peer.local_version)
65
71
  end
66
-
67
72
  end
68
-
69
73
  end
70
-
71
74
  end
@@ -180,8 +180,8 @@ module Bitcoin
180
180
  end
181
181
 
182
182
  def on_tx(tx)
183
- logger.info('receive tx message.')
184
- # TODO
183
+ logger.info("receive tx message. #{tx.build_json}")
184
+ peer.handle_tx(tx)
185
185
  end
186
186
 
187
187
  def on_not_found(not_found)
@@ -232,4 +232,4 @@ module Bitcoin
232
232
 
233
233
  end
234
234
  end
235
- end
235
+ end
@@ -137,9 +137,12 @@ module Bitcoin
137
137
  headers.headers.each do |header|
138
138
  break unless header.valid?
139
139
  entry = chain.append_header(header)
140
+ next unless entry
140
141
  @best_hash = entry.hash
141
142
  @best_height = entry.height
142
143
  end
144
+ pool.changed
145
+ pool.notify_observers(:header, {hash: @best_hash, height: @best_height})
143
146
  start_block_header_download if headers.headers.size > 0 # next header download
144
147
  end
145
148
 
@@ -148,6 +151,10 @@ module Bitcoin
148
151
  pool.handle_error(e)
149
152
  end
150
153
 
154
+ def unbind
155
+ pool.handle_close_peer(self)
156
+ end
157
+
151
158
  # close peer connection.
152
159
  def close(msg = '')
153
160
  conn.close(msg)
@@ -178,6 +185,11 @@ module Bitcoin
178
185
  conn.send_message(getdata)
179
186
  end
180
187
 
188
+ def handle_tx(tx)
189
+ pool.changed
190
+ pool.notify_observers(:tx, tx)
191
+ end
192
+
181
193
  # send ping message.
182
194
  def send_ping
183
195
  ping = Bitcoin::Message::Ping.new
@@ -11,6 +11,7 @@ module Bitcoin
11
11
 
12
12
  # peer pool class.
13
13
  class Pool
14
+ include Observable
14
15
 
15
16
  attr_reader :peers # active peers
16
17
  attr_reader :pending_peers # currently connecting peer
@@ -39,15 +40,8 @@ module Bitcoin
39
40
  logger.debug 'Start connecting other pears.'
40
41
  addr_list = peer_discovery.peers
41
42
 
42
- port = Bitcoin.chain_params.default_port
43
- EM::Iterator.new(addr_list, Bitcoin::PARALLEL_THREAD).each do |ip, iter|
44
- if pending_peers.size < MAX_OUTBOUND_CONNECTIONS
45
- peer = Peer.new(ip, port, self, @configuration)
46
- pending_peers << peer
47
- peer.connect
48
- iter.next
49
- end
50
- end
43
+ connect(addr_list)
44
+
51
45
  @started = true
52
46
  end
53
47
 
@@ -64,6 +58,14 @@ module Bitcoin
64
58
  filter_load(peer) if node.wallet
65
59
  end
66
60
 
61
+ def handle_close_peer(peer)
62
+ return unless started
63
+ peers.delete(peer)
64
+ pending_peers.delete(peer)
65
+ addr_list = peer_discovery.peers - peers.map(&:host) - pending_peers.map(&:host) - [peer.host]
66
+ connect(addr_list)
67
+ end
68
+
67
69
  # terminate peers.
68
70
  def terminate
69
71
  peers.each { |peer| peer.close('terminate') }
@@ -112,7 +114,18 @@ module Bitcoin
112
114
  id
113
115
  end
114
116
 
115
- end
117
+ def connect(addr_list)
118
+ port = Bitcoin.chain_params.default_port
116
119
 
120
+ EM::Iterator.new(addr_list, Bitcoin::PARALLEL_THREAD).each do |ip, iter|
121
+ if pending_peers.size + peers.size < MAX_OUTBOUND_CONNECTIONS
122
+ peer = Peer.new(ip, port, self, @configuration)
123
+ pending_peers << peer
124
+ peer.connect
125
+ iter.next
126
+ end
127
+ end
128
+ end
129
+ end
117
130
  end
118
131
  end
@@ -30,6 +30,18 @@ module Bitcoin
30
30
  request('getpeerinfo')
31
31
  end
32
32
 
33
+ desc 'decoderawtransaction "hexstring"', 'Return a JSON object representing the serialized, hex-encoded transaction.'
34
+ def decoderawtransaction(hexstring)
35
+ request('decoderawtransaction', hexstring)
36
+ end
37
+
38
+ desc 'decodescript "hexstring"', 'Decode a hex-encoded script.'
39
+ def decodescript(hexstring)
40
+ request('decodescript', hexstring)
41
+ end
42
+
43
+ # wallet cli
44
+
33
45
  desc 'sendrawtransaction', 'Submits raw transaction (serialized, hex-encoded) to local node and network.'
34
46
  def sendrawtransaction(hex_tx)
35
47
  request('sendrawtransaction', hex_tx)
@@ -60,13 +60,20 @@ module Bitcoin
60
60
  pool.filter_clear
61
61
  end
62
62
 
63
+ def add_observer(observer)
64
+ pool.add_observer(observer)
65
+ end
66
+
67
+ def delete_observer(observer)
68
+ pool.delete_observer(observer)
69
+ end
70
+
63
71
  private
64
72
 
65
73
  def setup_filter
66
74
  @bloom = Bitcoin::BloomFilter.create_filter(512, 0.01)
67
- wallet.watch_targets.each{|t|bloom.add(t.htb.reverse)} if wallet
75
+ wallet.watch_targets.each{|t|bloom.add(t.htb)} if wallet
68
76
  end
69
-
70
77
  end
71
78
  end
72
79
  end
@@ -116,8 +116,8 @@ module Bitcoin
116
116
  OP_CHECKMULTISIGVERIFY = 0xaf
117
117
 
118
118
  # https://en.bitcoin.it/wiki/Script#Locktime
119
- OP_NOP2 = OP_CHECKLOCKTIMEVERIFY = 0xb1
120
- OP_NOP3 = OP_CHECKSEQUENCEVERIFY = 0xb2
119
+ OP_NOP2 = OP_CHECKLOCKTIMEVERIFY = OP_CLTV = 0xb1
120
+ OP_NOP3 = OP_CHECKSEQUENCEVERIFY = OP_CSV = 0xb2
121
121
 
122
122
  # https://en.bitcoin.it/wiki/Script#Reserved_words
123
123
  OP_RESERVED = 0x50
@@ -136,7 +136,13 @@ module Bitcoin
136
136
  OP_NOP9 = 0xb8
137
137
  OP_NOP10 = 0xb9
138
138
 
139
- OPCODES_MAP = Hash[*constants.grep(/^OP_/).map { |c| [const_get(c), c.to_s] }.flatten]
139
+ # https://en.bitcoin.it/wiki/Script#Pseudo-words
140
+ OP_PUBKEYHASH = 0xfd
141
+ OP_PUBKEY = 0xfe
142
+ OP_INVALIDOPCODE = 0xff
143
+
144
+ DUPLICATE_KEY = [:OP_NOP2, :OP_NOP3]
145
+ OPCODES_MAP = Hash[*(constants.grep(/^OP_/) - [:OP_NOP2, :OP_NOP3, :OP_CHECKLOCKTIMEVERIFY, :OP_CHECKSEQUENCEVERIFY]).map { |c| [const_get(c), c.to_s] }.flatten]
140
146
  NAME_MAP = Hash[*constants.grep(/^OP_/).map { |c| [c.to_s, const_get(c)] }.flatten]
141
147
 
142
148
  def opcode_to_name(opcode)
@@ -14,12 +14,16 @@ module Bitcoin
14
14
  @index = index
15
15
  end
16
16
 
17
+ def self.from_txid(txid, index)
18
+ self.new(txid.rhex, index)
19
+ end
20
+
17
21
  def coinbase?
18
22
  hash == COINBASE_HASH && index == COINBASE_INDEX
19
23
  end
20
24
 
21
25
  def to_payload
22
- [hash.htb.reverse, index].pack('a32V')
26
+ [hash.htb, index].pack('a32V')
23
27
  end
24
28
 
25
29
  def self.create_coinbase_outpoint
@@ -30,6 +34,11 @@ module Bitcoin
30
34
  index >= 0 && (!coinbase? && hash != COINBASE_HASH)
31
35
  end
32
36
 
37
+ # convert hash to txid
38
+ def txid
39
+ hash.rhex
40
+ end
41
+
33
42
  end
34
43
 
35
44
  end
@@ -0,0 +1,20 @@
1
+ module Bitcoin
2
+ module Payments
3
+
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#Output
5
+ class Output < Protobuf::Message
6
+
7
+ optional :uint64, :amount, 1, {default: 0}
8
+
9
+ required :bytes, :script, 2
10
+
11
+ # convert to TxOut object.
12
+ # @return [Bitcoin::TxOut]
13
+ def to_tx_out
14
+ Bitcoin::TxOut.new(value: amount, script_pubkey: Bitcoin::Script.parse_from_payload(script))
15
+ end
16
+
17
+ end
18
+
19
+ end
20
+ end
@@ -0,0 +1,26 @@
1
+ module Bitcoin
2
+ module Payments
3
+
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#Payment
5
+ class Payment < Protobuf::Message
6
+
7
+ optional :bytes, :merchant_data, 1
8
+
9
+ repeated :bytes, :transactions, 2
10
+
11
+ repeated Bitcoin::Payments::Output, :refund_to, 3
12
+
13
+ optional :string, :memo, 4
14
+
15
+ def self.parse_from_payload(payload)
16
+ decode(payload)
17
+ end
18
+
19
+ def transactions
20
+ @values[:transactions].map{|raw_tx|Bitcoin::Tx.parse_from_payload(raw_tx)}
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+ end
@@ -0,0 +1,17 @@
1
+ module Bitcoin
2
+ module Payments
3
+
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#PaymentACK
5
+ class PaymentACK < Protobuf::Message
6
+
7
+ required Bitcoin::Payments::Payment, :payment, 1
8
+
9
+ optional :string, :memo, 2
10
+
11
+ def self.parse_from_payload(payload)
12
+ decode(payload)
13
+ end
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,24 @@
1
+ module Bitcoin
2
+ module Payments
3
+
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#PaymentDetailsPaymentRequest
5
+ class PaymentDetails < Protobuf::Message
6
+
7
+ optional :string, :network, 1, {default: 'main'}
8
+
9
+ repeated Bitcoin::Payments::Output, :outputs, 2
10
+
11
+ required :uint64, :time, 3
12
+
13
+ optional :uint64, :expires, 4
14
+
15
+ optional :string, :memo, 5
16
+
17
+ optional :string, :payment_url, 6
18
+
19
+ optional :bytes, :merchant_data, 7
20
+
21
+ end
22
+
23
+ end
24
+ end
@@ -0,0 +1,79 @@
1
+ module Bitcoin
2
+ module Payments
3
+
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#PaymentDetailsPaymentRequest
5
+ class PaymentRequest < Protobuf::Message
6
+
7
+ optional :uint32, :payment_details_version, 1, {default: 1}
8
+
9
+ optional :string, :pki_type, 2, {default: 'none'}
10
+
11
+ optional :bytes, :pki_data, 3
12
+
13
+ required :bytes, :serialized_payment_details, 4
14
+
15
+ optional :bytes, :signature, 5
16
+
17
+ def self.parse_from_payload(payload)
18
+ self.decode(payload)
19
+ end
20
+
21
+ # verify +pki_data+.
22
+ # @return [Struct] pki information.
23
+ def verify_pki_data
24
+ d = Struct.new(:display_name, :merchant_sign_key, :root_auth, :root_auth_name)
25
+ d
26
+ end
27
+
28
+ # get payment details
29
+ # @return [Bitcoin::Payments:PaymentDetails]
30
+ def details
31
+ PaymentDetails.decode(serialized_payment_details)
32
+ end
33
+
34
+ # get certificates
35
+ # @return [Array[OpenSSL::X509::Certificate]]
36
+ def certs
37
+ return [] unless has_pki?
38
+ X509Certificates.decode(pki_data).certs
39
+ end
40
+
41
+ # whether exist +pki_data+.
42
+ def has_pki?
43
+ pki_type != 'none'
44
+ end
45
+
46
+ # verify signature.
47
+ def valid_sig?
48
+ return false unless has_pki?
49
+ digest = case pki_type
50
+ when 'x509+sha256'
51
+ OpenSSL::Digest::SHA256.new
52
+ when 'x509+sha1'
53
+ OpenSSL::Digest::SHA1.new
54
+ else
55
+ raise "pki_type: #{pki_type} is invalid type."
56
+ end
57
+ certs.first.public_key.verify(digest, signature, sig_message)
58
+ end
59
+
60
+ # verify expire time for payment request.
61
+ def valid_time?
62
+ expires = details.expires
63
+ return true if expires == 0
64
+ Time.now.to_i <= expires
65
+ end
66
+
67
+ private
68
+
69
+ # Generate data to be signed
70
+ def sig_message
71
+ PaymentRequest.new(payment_details_version: payment_details_version,
72
+ pki_type: pki_type, pki_data: pki_data, signature: '',
73
+ serialized_payment_details: serialized_payment_details).encode
74
+ end
75
+
76
+ end
77
+
78
+ end
79
+ end
@@ -0,0 +1,18 @@
1
+ module Bitcoin
2
+ module Payments
3
+
4
+ # https://github.com/bitcoin/bips/blob/master/bip-0070.mediawiki#Certificates
5
+ class X509Certificates < Protobuf::Message
6
+
7
+ repeated :bytes, :certificate, 1
8
+
9
+ # get certificates
10
+ # @return [Array[OpenSSL::X509::Certificate]]
11
+ def certs
12
+ certificate.map{|v|OpenSSL::X509::Certificate.new(v)}
13
+ end
14
+
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,14 @@
1
+ require 'protobuf'
2
+
3
+ module Bitcoin
4
+ module Payments
5
+
6
+ autoload :Output, 'bitcoin/payments/output.pb'
7
+ autoload :Payment, 'bitcoin/payments/payment.pb'
8
+ autoload :PaymentACK, 'bitcoin/payments/payment_ack.pb'
9
+ autoload :PaymentDetails, 'bitcoin/payments/payment_details.pb'
10
+ autoload :PaymentRequest, 'bitcoin/payments/payment_request.pb'
11
+ autoload :X509Certificates, 'bitcoin/payments/x509_certificates.pb'
12
+
13
+ end
14
+ end
@@ -31,12 +31,12 @@ module Bitcoin
31
31
  hash: block_id,
32
32
  height: entry.height,
33
33
  version: entry.header.version,
34
- versionHex: entry.header.version.to_s(16),
34
+ versionHex: entry.header.version.to_even_length_hex,
35
35
  merkleroot: entry.header.merkle_root.rhex,
36
36
  time: entry.header.time,
37
37
  mediantime: node.chain.mtp(block_hash),
38
38
  nonce: entry.header.nonce,
39
- bits: entry.header.bits.to_s(16),
39
+ bits: entry.header.bits.to_even_length_hex,
40
40
  previousblockhash: entry.prev_hash.rhex,
41
41
  nextblockhash: node.chain.next_hash(block_hash).rhex
42
42
  }
@@ -53,7 +53,7 @@ module Bitcoin
53
53
  id: peer.id,
54
54
  addr: "#{peer.host}:#{peer.port}",
55
55
  addrlocal: local_addr,
56
- services: peer.remote_version.services.to_s(16).rjust(16, '0'),
56
+ services: peer.remote_version.services.to_even_length_hex.rjust(16, '0'),
57
57
  relaytxes: peer.remote_version.relay,
58
58
  lastsend: peer.last_send,
59
59
  lastrecv: peer.last_recv,
@@ -80,6 +80,28 @@ module Bitcoin
80
80
  tx.txid
81
81
  end
82
82
 
83
+ # decode tx data.
84
+ def decoderawtransaction(hex_tx)
85
+ begin
86
+ Bitcoin::Tx.parse_from_payload(hex_tx.htb).to_h
87
+ rescue Exception
88
+ raise ArgumentError.new('TX decode failed')
89
+ end
90
+ end
91
+
92
+ # decode script data.
93
+ def decodescript(hex_script)
94
+ begin
95
+ script = Bitcoin::Script.parse_from_payload(hex_script.htb)
96
+ h = script.to_h
97
+ h.delete(:hex)
98
+ h[:p2sh] = script.to_p2sh.addresses.first unless script.p2sh?
99
+ h
100
+ rescue Exception
101
+ raise ArgumentError.new('Script decode failed')
102
+ end
103
+ end
104
+
83
105
  # wallet api
84
106
 
85
107
  # create wallet
@@ -67,7 +67,7 @@ module Bitcoin
67
67
  if opcode
68
68
  script << (v =~ /^\d/ && Opcodes.small_int_to_opcode(v.ord) ? v.ord : opcode)
69
69
  else
70
- script << v
70
+ script << (v =~ /^[0-9]+$/ ? v.to_i : v)
71
71
  end
72
72
  end
73
73
  script
@@ -116,10 +116,12 @@ module Bitcoin
116
116
  to_payload.bth
117
117
  end
118
118
 
119
- def to_addr
120
- return p2pkh_addr if p2pkh?
121
- return p2sh_addr if p2sh?
122
- return bech32_addr if witness_program?
119
+ def addresses
120
+ return [p2pkh_addr] if p2pkh?
121
+ return [p2sh_addr] if p2sh?
122
+ return [bech32_addr] if witness_program?
123
+ return get_multisig_pubkeys.map{|pubkey| Bitcoin::Key.new(pubkey: pubkey.bth).to_p2pkh} if multisig?
124
+ []
123
125
  end
124
126
 
125
127
  # check whether standard script.
@@ -272,7 +274,16 @@ module Bitcoin
272
274
  when String
273
275
  if c.pushdata?
274
276
  v = Opcodes.opcode_to_small_int(c.ord)
275
- v ? v : c.pushed_data.bth
277
+ if v
278
+ v
279
+ else
280
+ data = c.pushed_data
281
+ if data.bytesize <= 4
282
+ Script.decode_number(data.bth) # for scriptnum
283
+ else
284
+ data.bth
285
+ end
286
+ end
276
287
  else
277
288
  Opcodes.opcode_to_name(c.ord)
278
289
  end
@@ -409,6 +420,25 @@ module Bitcoin
409
420
  chunks == other.chunks
410
421
  end
411
422
 
423
+ def type
424
+ return 'pubkeyhash' if p2pkh?
425
+ return 'scripthash' if p2sh?
426
+ return 'multisig' if multisig?
427
+ return 'witness_v0_keyhash' if p2wpkh?
428
+ return 'witness_v0_scripthash' if p2wsh?
429
+ 'nonstandard'
430
+ end
431
+
432
+ def to_h
433
+ h = {asm: to_s, hex: to_payload.bth, type: type}
434
+ addrs = addresses
435
+ unless addrs.empty?
436
+ h[:req_sigs] = multisig? ? Bitcoin::Opcodes.opcode_to_small_int(chunks[0].bth.to_i(16)) :addrs.size
437
+ h[:addresses] = addrs
438
+ end
439
+ h
440
+ end
441
+
412
442
  private
413
443
 
414
444
  # generate p2pkh address. if script dose not p2pkh, return nil.
@@ -92,6 +92,10 @@ module Bitcoin
92
92
  'Witness provided for non-witness script'
93
93
  when SCRIPT_ERR_WITNESS_PUBKEYTYPE
94
94
  'Using non-compressed keys in segwit'
95
+ when SCRIPT_ERR_OP_CODESEPARATOR
96
+ 'Using OP_CODESEPARATOR in non-witness scrip'
97
+ when SCRIPT_ERR_SIG_FINDANDDELETE
98
+ 'Signature is found in scriptCode'
95
99
  when SCRIPT_ERR_UNKNOWN_ERROR, SCRIPT_ERR_ERROR_COUNT
96
100
  'unknown error'
97
101
  else
@@ -156,6 +156,7 @@ module Bitcoin
156
156
  return set_error(SCRIPT_ERR_OP_COUNT)
157
157
  end
158
158
  return set_error(SCRIPT_ERR_DISABLED_OPCODE) if DISABLE_OPCODES.include?(opcode)
159
+ return set_error(SCRIPT_ERR_OP_CODESEPARATOR) if opcode == OP_CODESEPARATOR && sig_version == :base && flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE)
159
160
  next unless (need_exec || (OP_IF <= opcode && opcode <= OP_ENDIF))
160
161
  small_int = Opcodes.opcode_to_small_int(opcode)
161
162
  if small_int && opcode != OP_0
@@ -396,7 +397,9 @@ module Bitcoin
396
397
 
397
398
  subscript = script.subscript(last_code_separator_index..-1)
398
399
  if sig_version == :base
399
- subscript = subscript.find_and_delete(Script.new << sig)
400
+ tmp = subscript.find_and_delete(Script.new << sig)
401
+ return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
402
+ subscript = tmp
400
403
  end
401
404
 
402
405
  return false if !check_pubkey_encoding(pubkey, sig_version) || !check_signature_encoding(sig) # error already set.
@@ -445,7 +448,9 @@ module Bitcoin
445
448
 
446
449
  if sig_version == :base
447
450
  sigs.each do |sig|
448
- subscript = subscript.find_and_delete(Script.new << sig)
451
+ tmp = subscript.find_and_delete(Script.new << sig)
452
+ return set_error(SCRIPT_ERR_SIG_FINDANDDELETE) if flag?(SCRIPT_VERIFY_CONST_SCRIPTCODE) && tmp != subscript
453
+ subscript = tmp
449
454
  end
450
455
  end
451
456
 
@@ -47,9 +47,8 @@ module Bitcoin
47
47
 
48
48
  return nil if s.zero?
49
49
 
50
- signature = ECDSA::Signature.new(r, s)
50
+ signature = ECDSA::Signature.new(r, s).to_der
51
51
  public_key = Bitcoin::Key.new(priv_key: privkey.bth).pubkey
52
- signature = ECDSA::Format::SignatureDerString.encode(signature) # signature with DER format
53
52
  raise 'Creation of signature failed.' unless Bitcoin::Secp256k1::Ruby.verify_sig(data, signature, public_key)
54
53
  signature
55
54
  end
@@ -43,7 +43,7 @@ module Bitcoin
43
43
  # @param [Bitcoin::BlockHeader] header a block header.
44
44
  # @return [Bitcoin::Store::ChainEntry] appended block header entry.
45
45
  def append_header(header)
46
- logger.info("append header #{header.hash}")
46
+ logger.info("append header #{header.block_id}")
47
47
  raise "this header is invalid. #{header.hash}" unless header.valid?
48
48
  best_block = latest_block
49
49
  current_height = best_block.height
data/lib/bitcoin/tx.rb CHANGED
@@ -109,6 +109,10 @@ module Bitcoin
109
109
  !inputs.find { |i| !i.script_witness.empty? }.nil?
110
110
  end
111
111
 
112
+ def ==(other)
113
+ hash == other.hash
114
+ end
115
+
112
116
  # serialize tx with old tx format
113
117
  def serialize_old_format
114
118
  buf = [version].pack('V')
@@ -219,6 +223,13 @@ module Bitcoin
219
223
  end
220
224
  end
221
225
 
226
+ def to_h
227
+ {
228
+ txid: txid, hash: witness_hash.rhex, version: version, size: size, vsize: vsize, locktime: lock_time,
229
+ vin: inputs.map(&:to_h), vout: outputs.map.with_index{|tx_out, index| tx_out.to_h.merge({n: index})}
230
+ }
231
+ end
232
+
222
233
  private
223
234
 
224
235
  # generate sighash with legacy format
data/lib/bitcoin/tx_in.rb CHANGED
@@ -35,7 +35,7 @@ module Bitcoin
35
35
  buf = payload.is_a?(String) ? StringIO.new(payload) : payload
36
36
  i = new
37
37
  hash, index = buf.read(36).unpack('a32V')
38
- i.out_point = OutPoint.new(hash.reverse.bth, index)
38
+ i.out_point = OutPoint.new(hash.bth, index)
39
39
  sig_length = Bitcoin.unpack_var_int_from_io(buf)
40
40
  sig = buf.read(sig_length)
41
41
  if i.coinbase?
@@ -62,6 +62,15 @@ module Bitcoin
62
62
  !script_witness.empty?
63
63
  end
64
64
 
65
+ def to_h
66
+ sig = script_sig.to_h
67
+ sig.delete(:type)
68
+ h = {txid: out_point.txid, vout: out_point.index, script_sig: sig }
69
+ h[:txinwitness] = script_witness.stack.map(&:bth) if has_witness?
70
+ h[:sequence] = sequence
71
+ h
72
+ end
73
+
65
74
  end
66
75
 
67
76
  end
@@ -32,6 +32,15 @@ module Bitcoin
32
32
  'ffffffffffffffff00'.htb
33
33
  end
34
34
 
35
+ # convert satoshi to btc
36
+ def value_to_btc
37
+ value / 100000000.0
38
+ end
39
+
40
+ def to_h
41
+ {value: value_to_btc, script_pubkey: script_pubkey.to_h}
42
+ end
43
+
35
44
  end
36
45
 
37
46
  end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.1.7"
2
+ VERSION = "0.1.8"
3
3
  end
@@ -53,7 +53,7 @@ module Bitcoin
53
53
  end
54
54
 
55
55
  # create new receive key
56
- # @return [Bitcoin::ExtKey]
56
+ # @return [Bitcoin::ExtPubkey]
57
57
  def create_receive
58
58
  @receive_depth += 1
59
59
  save
@@ -61,7 +61,7 @@ module Bitcoin
61
61
  end
62
62
 
63
63
  # create new change key
64
- # # @return [Bitcoin::ExtKey]
64
+ # @return [Bitcoin::ExtPubkey]
65
65
  def create_change
66
66
  @change_depth += 1
67
67
  save
data/lib/bitcoin.rb CHANGED
@@ -8,6 +8,7 @@ require 'securerandom'
8
8
  require 'json'
9
9
  require 'bech32'
10
10
  require 'ffi'
11
+ require 'observer'
11
12
  require 'tmpdir'
12
13
  require_relative 'openassets'
13
14
 
@@ -44,6 +45,7 @@ module Bitcoin
44
45
  autoload :RPC, 'bitcoin/rpc'
45
46
  autoload :Wallet, 'bitcoin/wallet'
46
47
  autoload :BloomFilter, 'bitcoin/bloom_filter'
48
+ autoload :Payments, 'bitcoin/payments'
47
49
 
48
50
  require_relative 'bitcoin/constants'
49
51
 
@@ -180,4 +182,12 @@ module Bitcoin
180
182
  hex.rjust((hex.length / 2.0).ceil * 2, '0')
181
183
  end
182
184
  end
185
+
186
+ class ::ECDSA::Signature
187
+ # convert signature to der string.
188
+ def to_der
189
+ ECDSA::Format::SignatureDerString.encode(self)
190
+ end
191
+ end
192
+
183
193
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bitcoinrb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-03-01 00:00:00.000000000 Z
11
+ date: 2018-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa
@@ -123,7 +123,7 @@ dependencies:
123
123
  - !ruby/object:Gem::Version
124
124
  version: 1.0.0
125
125
  - !ruby/object:Gem::Dependency
126
- name: leveldb-ruby
126
+ name: eventmachine_httpserver
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
@@ -137,7 +137,7 @@ dependencies:
137
137
  - !ruby/object:Gem::Version
138
138
  version: '0'
139
139
  - !ruby/object:Gem::Dependency
140
- name: eventmachine_httpserver
140
+ name: rest-client
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - ">="
@@ -151,7 +151,7 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: '0'
153
153
  - !ruby/object:Gem::Dependency
154
- name: rest-client
154
+ name: iniparse
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - ">="
@@ -165,7 +165,7 @@ dependencies:
165
165
  - !ruby/object:Gem::Version
166
166
  version: '0'
167
167
  - !ruby/object:Gem::Dependency
168
- name: iniparse
168
+ name: siphash
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - ">="
@@ -179,7 +179,7 @@ dependencies:
179
179
  - !ruby/object:Gem::Version
180
180
  version: '0'
181
181
  - !ruby/object:Gem::Dependency
182
- name: siphash
182
+ name: protobuf
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - ">="
@@ -192,6 +192,20 @@ dependencies:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
194
  version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: leveldb-ruby
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
195
209
  - !ruby/object:Gem::Dependency
196
210
  name: bundler
197
211
  requirement: !ruby/object:Gem::Requirement
@@ -344,6 +358,13 @@ files:
344
358
  - lib/bitcoin/node/spv.rb
345
359
  - lib/bitcoin/opcodes.rb
346
360
  - lib/bitcoin/out_point.rb
361
+ - lib/bitcoin/payments.rb
362
+ - lib/bitcoin/payments/output.pb.rb
363
+ - lib/bitcoin/payments/payment.pb.rb
364
+ - lib/bitcoin/payments/payment_ack.pb.rb
365
+ - lib/bitcoin/payments/payment_details.pb.rb
366
+ - lib/bitcoin/payments/payment_request.pb.rb
367
+ - lib/bitcoin/payments/x509_certificates.pb.rb
347
368
  - lib/bitcoin/rpc.rb
348
369
  - lib/bitcoin/rpc/http_server.rb
349
370
  - lib/bitcoin/rpc/request_handler.rb