bitcoinrb 0.4.0 → 0.5.0

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 (52) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +1 -0
  4. data/README.md +3 -2
  5. data/bitcoinrb.gemspec +2 -2
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +10 -1
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_header.rb +2 -0
  10. data/lib/bitcoin/ext.rb +5 -0
  11. data/lib/bitcoin/ext/json_parser.rb +46 -0
  12. data/lib/bitcoin/ext_key.rb +7 -3
  13. data/lib/bitcoin/key_path.rb +12 -5
  14. data/lib/bitcoin/message.rb +7 -0
  15. data/lib/bitcoin/message/base.rb +1 -0
  16. data/lib/bitcoin/message/cf_parser.rb +16 -0
  17. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  18. data/lib/bitcoin/message/cfheaders.rb +40 -0
  19. data/lib/bitcoin/message/cfilter.rb +35 -0
  20. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  21. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  22. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  23. data/lib/bitcoin/message/version.rb +7 -0
  24. data/lib/bitcoin/mnemonic.rb +5 -5
  25. data/lib/bitcoin/network/peer.rb +9 -4
  26. data/lib/bitcoin/network/peer_discovery.rb +3 -1
  27. data/lib/bitcoin/node/cli.rb +14 -10
  28. data/lib/bitcoin/node/spv.rb +1 -1
  29. data/lib/bitcoin/out_point.rb +2 -0
  30. data/lib/bitcoin/payment_code.rb +92 -0
  31. data/lib/bitcoin/psbt/input.rb +5 -14
  32. data/lib/bitcoin/psbt/tx.rb +7 -12
  33. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  34. data/lib/bitcoin/rpc/request_handler.rb +1 -1
  35. data/lib/bitcoin/script/script.rb +5 -9
  36. data/lib/bitcoin/script/script_interpreter.rb +2 -3
  37. data/lib/bitcoin/secp256k1.rb +1 -0
  38. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  39. data/lib/bitcoin/secp256k1/ruby.rb +4 -35
  40. data/lib/bitcoin/store.rb +1 -0
  41. data/lib/bitcoin/store/chain_entry.rb +1 -0
  42. data/lib/bitcoin/store/utxo_db.rb +226 -0
  43. data/lib/bitcoin/tx.rb +3 -7
  44. data/lib/bitcoin/tx_in.rb +3 -4
  45. data/lib/bitcoin/util.rb +7 -0
  46. data/lib/bitcoin/version.rb +1 -1
  47. data/lib/bitcoin/wallet.rb +1 -0
  48. data/lib/bitcoin/wallet/account.rb +1 -0
  49. data/lib/bitcoin/wallet/base.rb +2 -2
  50. data/lib/bitcoin/wallet/master_key.rb +1 -0
  51. data/lib/bitcoin/wallet/utxo.rb +37 -0
  52. metadata +34 -20
@@ -0,0 +1,35 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # cfilter message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#cfilter
6
+ class CFilter < Base
7
+
8
+ COMMAND = 'cfilter'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :block_hash # little endian
12
+ attr_accessor :filter # little endian
13
+
14
+ def initialize(filter_type, block_hash, filter)
15
+ @filter_type = filter_type
16
+ @block_hash = block_hash
17
+ @filter = filter
18
+ end
19
+
20
+ def self.parse_from_payload(payload)
21
+ buf = StringIO.new(payload)
22
+ type = buf.read(1).unpack("C").first
23
+ hash = buf.read(32).bth
24
+ len = Bitcoin.unpack_var_int_from_io(buf)
25
+ filter = buf.read(len).bth
26
+ self.new(type, hash, filter)
27
+ end
28
+
29
+ def to_payload
30
+ [filter_type, block_hash].pack('CH*') + Bitcoin.pack_var_string(filter.htb)
31
+ end
32
+ end
33
+
34
+ end
35
+ end
@@ -0,0 +1,29 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfcheckpt message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfcheckpt
6
+ class GetCFCheckpt < Base
7
+
8
+ COMMAND = 'getcfcheckpt'
9
+
10
+ attr_accessor :filter_type
11
+ attr_accessor :stop_hash # little endian
12
+
13
+ def initialize(filter_type, stop_hash)
14
+ @filter_type = filter_type
15
+ @stop_hash = stop_hash
16
+ end
17
+
18
+ def self.parse_from_payload(payload)
19
+ type, hash = payload.unpack('CH*')
20
+ self.new(type, hash)
21
+ end
22
+
23
+ def to_payload
24
+ [filter_type, stop_hash].pack('CH*')
25
+ end
26
+
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,24 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfheaders message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfheaders
6
+ class GetCFHeaders < Base
7
+ include CFParser
8
+ extend CFParser
9
+
10
+ COMMAND = 'getcfheaders'
11
+
12
+ attr_accessor :filter_type
13
+ attr_accessor :start_height
14
+ attr_accessor :stop_hash # little endian
15
+
16
+ def initialize(filter_type, start_height, stop_hash)
17
+ @filter_type = filter_type
18
+ @start_height = start_height
19
+ @stop_hash = stop_hash
20
+ end
21
+
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,25 @@
1
+ module Bitcoin
2
+ module Message
3
+
4
+ # getcfilters message for BIP-157
5
+ # https://github.com/bitcoin/bips/blob/master/bip-0157.mediawiki#getcfilters
6
+ class GetCFilters < Base
7
+ include CFParser
8
+ extend CFParser
9
+
10
+ COMMAND = 'getcfilters'
11
+
12
+ attr_accessor :filter_type
13
+ attr_accessor :start_height
14
+ attr_accessor :stop_hash # little endian
15
+
16
+ def initialize(filter_type, start_height, stop_hash)
17
+ @filter_type = filter_type
18
+ @start_height = start_height
19
+ @stop_hash = stop_hash
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+ end
@@ -64,6 +64,13 @@ module Bitcoin
64
64
  ( version >= 70001 && payload ) ? unpack_boolean(payload) : [ true, nil ]
65
65
  end
66
66
 
67
+ # Check whether +service_flag+ support this version.
68
+ # @param [Integer] service_flag the service flags.
69
+ # @return [Boolean] whether support +service_flag+
70
+ def support?(service_flag)
71
+ (services & service_flag) != 0
72
+ end
73
+
67
74
  end
68
75
  end
69
76
  end
@@ -6,11 +6,11 @@ module Bitcoin
6
6
 
7
7
  WORD_DIR = "#{__dir__}/mnemonic/wordlist"
8
8
 
9
- attr_reader :word_list
9
+ attr_reader :language
10
10
 
11
- def initialize(word_list)
12
- raise ArgumentError, 'specified language is not supported.' unless Mnemonic.word_lists.include?(word_list)
13
- @word_list = word_list
11
+ def initialize(language)
12
+ raise ArgumentError, 'specified language is not supported.' unless Mnemonic.word_lists.include?(language)
13
+ @language = language
14
14
  end
15
15
 
16
16
  # get support language list
@@ -69,7 +69,7 @@ module Bitcoin
69
69
 
70
70
  # load word list contents
71
71
  def load_words
72
- File.readlines("#{WORD_DIR}/#{word_list}.txt").map(&:strip)
72
+ File.readlines("#{WORD_DIR}/#{language}.txt").map(&:strip)
73
73
  end
74
74
 
75
75
  end
@@ -83,10 +83,15 @@ module Bitcoin
83
83
 
84
84
  def post_handshake
85
85
  @connected = true
86
- pool.handle_new_peer(self)
87
- # require remote peer to use headers message instead fo inv message.
88
- conn.send_message(Bitcoin::Message::SendHeaders.new)
89
- EM.add_periodic_timer(PING_INTERVAL) {send_ping}
86
+ if remote_version.support?(Bitcoin::Message::SERVICE_FLAGS[:bloom])
87
+ pool.handle_new_peer(self)
88
+ # require remote peer to use headers message instead fo inv message.
89
+ conn.send_message(Bitcoin::Message::SendHeaders.new)
90
+ EM.add_periodic_timer(PING_INTERVAL) {send_ping}
91
+ else
92
+ close("peer does not support NODE_BLOOM.")
93
+ pool.pending_peers.delete(self)
94
+ end
90
95
  end
91
96
 
92
97
  # start block header download
@@ -30,7 +30,9 @@ module Bitcoin
30
30
  logger.debug 'discover peer address from DNS seeds.'
31
31
  dns_seeds.map { |seed|
32
32
  begin
33
- Socket.getaddrinfo(seed, Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
33
+ # x5 is a prefix for finding nodes that support NODE_BLOOM.
34
+ # https://github.com/bitcoin/bitcoin/pull/8083#issuecomment-221552835
35
+ Socket.getaddrinfo("x5.#{seed}", Bitcoin.chain_params.default_port).map{|a|a[2]}.uniq
34
36
  rescue SocketError => e
35
37
  logger.error "SocketError occurred when load DNS seed: #{seed}, error: #{e.message}"
36
38
  nil
@@ -1,4 +1,4 @@
1
- require 'rest-client'
1
+ require 'net/http'
2
2
  require 'thor'
3
3
  require 'json'
4
4
 
@@ -92,15 +92,19 @@ module Bitcoin
92
92
  :id => 'jsonrpc'
93
93
  }
94
94
  begin
95
- RestClient::Request.execute(method: :post, url: config.server_url, payload: data.to_json,
96
- headers: {content_type: :json}) do |response, request, result|
97
- return false if !result.kind_of?(Net::HTTPSuccess) && response.empty?
98
- begin
99
- json = JSON.parse(response.to_str)
100
- puts JSON.pretty_generate(json)
101
- rescue Exception
102
- puts response.to_str
103
- end
95
+ uri = URI.parse(config.server_url)
96
+ http = Net::HTTP.new(uri.hostname, uri.port)
97
+ http.use_ssl = uri.scheme === "https"
98
+ request = Net::HTTP::Post.new('/')
99
+ request.content_type = 'application/json'
100
+ request.body = data.to_json
101
+ response = http.request(request)
102
+ body = response.body
103
+ begin
104
+ json = JSON.parse(body.to_str)
105
+ puts JSON.pretty_generate(json)
106
+ rescue Exception
107
+ puts body.to_str
104
108
  end
105
109
  rescue Exception => e
106
110
  puts e.message
@@ -45,7 +45,7 @@ module Bitcoin
45
45
  # broadcast a transaction
46
46
  def broadcast(tx)
47
47
  pool.broadcast(tx)
48
- logger.debug "broadcast tx: #{tx.to_payload.bth}"
48
+ logger.debug "broadcast tx: #{tx.to_hex}"
49
49
  end
50
50
 
51
51
  # add filter element to bloom filter.
@@ -3,6 +3,8 @@ module Bitcoin
3
3
  # outpoint class
4
4
  class OutPoint
5
5
 
6
+ include Bitcoin::HexConverter
7
+
6
8
  COINBASE_HASH = '0000000000000000000000000000000000000000000000000000000000000000'
7
9
  COINBASE_INDEX = 4294967295
8
10
 
@@ -0,0 +1,92 @@
1
+ module Bitcoin
2
+
3
+ # BIP47 payment code
4
+ class PaymentCode < ExtKey
5
+
6
+ include Bitcoin::HexConverter
7
+
8
+ attr_accessor :x_value
9
+ attr_accessor :sign
10
+
11
+ VERSION_BYTE = '47'
12
+ SUPPORT_VERSIONS = ['01']
13
+ SUPPORT_SIGNS = ['02', '03']
14
+
15
+ def initialize
16
+ @version = '01'
17
+ @features_bits = '00'
18
+ @reserve_field = '0' * 26
19
+ end
20
+
21
+ # generate master key from seed.
22
+ # @params [String] seed a seed data with hex format.
23
+ def self.generate_master(seed)
24
+ master_ext_key = super.derive(47, harden=true).derive(0, harden=true).derive(0, harden=true)
25
+ compressed_pubkey = master_ext_key.pub
26
+
27
+ payment_code = PaymentCode.new
28
+ payment_code.depth = master_ext_key.depth
29
+ payment_code.key = master_ext_key.key
30
+ payment_code.sign = compressed_pubkey[0..1]
31
+ payment_code.x_value = compressed_pubkey[2..-1]
32
+ payment_code.chain_code = master_ext_key.chain_code
33
+ payment_code
34
+ end
35
+
36
+ # Base58 encoded payment code
37
+ def to_base58
38
+ payment_code_with_version_byte = VERSION_BYTE + to_hex
39
+ Bitcoin::Base58.encode(payment_code_with_version_byte + Bitcoin.calc_checksum(payment_code_with_version_byte))
40
+ end
41
+
42
+ # serialize payment code
43
+ def to_payload
44
+ @version.htb << @features_bits.htb << @sign.htb << @x_value.htb << @chain_code << @reserve_field.htb
45
+ end
46
+
47
+ # get notification address
48
+ def notification_address
49
+ ext_pubkey.derive(0).addr
50
+ end
51
+
52
+ # decode base58 encoded payment code
53
+ # @params [String] base58_payment_code base58 encoded payment code
54
+ def self.from_base58(base58_payment_code)
55
+ hex = Bitcoin::Base58.decode(base58_payment_code)
56
+ version = hex[2..3]
57
+ sign = hex[6..7]
58
+ public_key = hex[8..71]
59
+ payment_code = hex[0...-8]
60
+
61
+ raise ArgumentError, 'invalid version byte' unless hex[0..1] == VERSION_BYTE
62
+ raise ArgumentError, 'invalid version' unless PaymentCode.support_version?(version)
63
+ raise ArgumentError, 'invalid sign' unless PaymentCode.support_sign?(sign)
64
+ raise ArgumentError, 'invalid public key' unless Bitcoin::Key.new(priv_key: nil, pubkey: sign + public_key).fully_valid_pubkey?
65
+ raise ArgumentError, 'invalid checksum' unless Bitcoin.calc_checksum(payment_code) == hex[-8..-1]
66
+
67
+ x_value = payment_code[8..71]
68
+ chain_code_hex = payment_code[72..135]
69
+
70
+ payment_code_pubkey = PaymentCode.new
71
+ payment_code_pubkey.depth = 3
72
+ payment_code_pubkey.sign = sign
73
+ payment_code_pubkey.x_value = x_value
74
+ payment_code_pubkey.chain_code = [chain_code_hex].pack('H*')
75
+
76
+ payment_code_pubkey.to_payload
77
+ end
78
+
79
+ # check whether +version+ is supported version bytes.
80
+ def self.support_version?(version)
81
+ SUPPORT_VERSIONS.include?(version)
82
+ end
83
+
84
+ # check whether +sign+ is supported version bytes.
85
+ def self.support_sign?(sign)
86
+ SUPPORT_SIGNS.include?(sign)
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+
@@ -93,7 +93,8 @@ module Bitcoin
93
93
 
94
94
  def to_payload
95
95
  payload = ''
96
- payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value: non_witness_utxo.to_payload) if non_witness_utxo
96
+ payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:non_witness_utxo], value:
97
+ (witness_utxo && valid_witness_input?) ? non_witness_utxo.serialize_old_format : non_witness_utxo.to_payload) if non_witness_utxo
97
98
  payload << PSBT.serialize_to_vector(PSBT_IN_TYPES[:witness_utxo], value: witness_utxo.to_payload) if witness_utxo
98
99
  if final_script_sig.nil? && final_script_witness.nil?
99
100
  payload << partial_sigs.map{|k, v|PSBT.serialize_to_vector(PSBT_IN_TYPES[:partial_sig], key: k.htb, value: v)}.join
@@ -109,15 +110,6 @@ module Bitcoin
109
110
  payload
110
111
  end
111
112
 
112
- # Sanity check
113
- # @return [Boolean]
114
- def sane?
115
- return false if non_witness_utxo && witness_utxo
116
- return false if witness_script && witness_utxo.nil?
117
- return false if final_script_witness && witness_utxo.nil?
118
- true
119
- end
120
-
121
113
  # Check whether input's scriptPubkey is correct witness.
122
114
  # @return [Boolean]
123
115
  def valid_witness_input?
@@ -141,7 +133,6 @@ module Bitcoin
141
133
  # @param [Bitcoin::TxOut] utxo utxo object which input refers.
142
134
  # @return [Boolean]
143
135
  def ready_to_sign?(utxo)
144
- return false unless sane?
145
136
  return valid_witness_input? if witness_utxo
146
137
  valid_non_witness_input?(utxo) # non_witness_utxo
147
138
  end
@@ -177,8 +168,8 @@ module Bitcoin
177
168
  combined.witness_script = witness_script
178
169
  combined.sighash_type = sighash_type
179
170
  sigs = Hash[partial_sigs.merge(psbi.partial_sigs)]
180
- redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script && redeem_script.multisig?
181
- witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script && witness_script.multisig?
171
+ redeem_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if redeem_script&.multisig?
172
+ witness_script.get_multisig_pubkeys.each{|pubkey|combined.partial_sigs[pubkey.bth] = sigs[pubkey.bth]} if witness_script&.multisig?
182
173
  combined.hd_key_paths = hd_key_paths.merge(psbi.hd_key_paths)
183
174
  combined
184
175
  end
@@ -190,7 +181,7 @@ module Bitcoin
190
181
  if non_witness_utxo
191
182
  self.final_script_sig = Bitcoin::Script.new << Bitcoin::Opcodes::OP_0 if redeem_script.multisig?
192
183
  partial_sigs.values.each {|sig|final_script_sig << sig}
193
- final_script_sig << redeem_script.to_payload.bth
184
+ final_script_sig << redeem_script.to_hex
194
185
  self.partial_sigs = {}
195
186
  self.hd_key_paths = {}
196
187
  self.redeem_script = nil
@@ -2,6 +2,7 @@ module Bitcoin
2
2
  module PSBT
3
3
 
4
4
  class GlobalXpub
5
+ include Bitcoin::HexConverter
5
6
 
6
7
  attr_reader :xpub # Bitcoin::ExtPubkey
7
8
  attr_reader :info # Bitcoin::PSBT::KeyOriginInfo
@@ -16,7 +17,7 @@ module Bitcoin
16
17
  end
17
18
 
18
19
  def to_h
19
- {xpub: xpub.to_payload.bth}.merge(info.to_h)
20
+ {xpub: xpub.to_hex}.merge(info.to_h)
20
21
  end
21
22
 
22
23
  def to_s
@@ -25,6 +26,8 @@ module Bitcoin
25
26
  end
26
27
 
27
28
  class Tx
29
+ include Bitcoin::HexConverter
30
+
28
31
  attr_accessor :tx
29
32
  attr_accessor :xpubs
30
33
  attr_reader :inputs
@@ -117,10 +120,6 @@ module Bitcoin
117
120
 
118
121
  raise ArgumentError, 'Outputs provided does not match the number of outputs in transaction.' unless partial_tx.outputs.size == partial_tx.tx.out.size
119
122
 
120
- partial_tx.inputs.each do |input|
121
- raise ArgumentError, 'PSBT is not sane.' unless input.sane?
122
- end
123
-
124
123
  partial_tx
125
124
  end
126
125
 
@@ -149,11 +148,9 @@ module Bitcoin
149
148
  payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:unsigned_tx], value: tx.to_payload)
150
149
  payload << xpubs.map(&:to_payload).join
151
150
  payload << PSBT.serialize_to_vector(PSBT_GLOBAL_TYPES[:ver], value: [version_number].pack('V')) if version_number
152
-
153
151
  payload << unknowns.map {|k,v|Bitcoin.pack_var_int(k.htb.bytesize) << k.htb << Bitcoin.pack_var_int(v.bytesize) << v}.join
154
152
 
155
153
  payload << PSBT_SEPARATOR.itb
156
-
157
154
  payload << inputs.map(&:to_payload).join
158
155
  payload << outputs.map(&:to_payload).join
159
156
  payload
@@ -177,14 +174,12 @@ module Bitcoin
177
174
  utxo = prev_tx.out[tx_in.out_point.index]
178
175
  raise ArgumentError, 'redeem script does not match utxo.' if redeem_script && !utxo.script_pubkey.include?(redeem_script.to_hash160)
179
176
  raise ArgumentError, 'witness script does not match redeem script.' if redeem_script && witness_script && !redeem_script.include?(witness_script.to_sha256)
180
- if utxo.script_pubkey.witness_program? || (redeem_script && redeem_script.witness_program?)
181
- inputs[i].witness_utxo = utxo
182
- else
183
- inputs[i].non_witness_utxo = prev_tx
184
- end
177
+ inputs[i].witness_utxo = utxo if utxo.script_pubkey.witness_program? || redeem_script&.witness_program?
178
+ inputs[i].non_witness_utxo = prev_tx
185
179
  inputs[i].redeem_script = redeem_script if redeem_script
186
180
  inputs[i].witness_script = witness_script if witness_script
187
181
  inputs[i].hd_key_paths = hd_key_paths.map(&:pubkey).zip(hd_key_paths).to_h
182
+ break
188
183
  end
189
184
  end
190
185
  end