bitcoinrb 0.1.7 → 0.1.8

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 (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