bitcoinrb 0.2.9 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.ruby-version +1 -1
  3. data/.travis.yml +3 -2
  4. data/README.md +7 -6
  5. data/bitcoinrb.gemspec +4 -4
  6. data/exe/bitcoinrbd +5 -0
  7. data/lib/bitcoin.rb +33 -1
  8. data/lib/bitcoin/bip85_entropy.rb +111 -0
  9. data/lib/bitcoin/block_header.rb +2 -0
  10. data/lib/bitcoin/chain_params.rb +0 -8
  11. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  12. data/lib/bitcoin/chainparams/testnet.yml +1 -1
  13. data/lib/bitcoin/constants.rb +3 -10
  14. data/lib/bitcoin/descriptor.rb +147 -0
  15. data/lib/bitcoin/ext.rb +5 -0
  16. data/lib/bitcoin/ext/json_parser.rb +46 -0
  17. data/lib/bitcoin/ext_key.rb +19 -4
  18. data/lib/bitcoin/key.rb +9 -5
  19. data/lib/bitcoin/key_path.rb +12 -5
  20. data/lib/bitcoin/message.rb +7 -0
  21. data/lib/bitcoin/message/base.rb +1 -0
  22. data/lib/bitcoin/message/cf_parser.rb +16 -0
  23. data/lib/bitcoin/message/cfcheckpt.rb +36 -0
  24. data/lib/bitcoin/message/cfheaders.rb +40 -0
  25. data/lib/bitcoin/message/cfilter.rb +35 -0
  26. data/lib/bitcoin/message/get_cfcheckpt.rb +29 -0
  27. data/lib/bitcoin/message/get_cfheaders.rb +24 -0
  28. data/lib/bitcoin/message/get_cfilters.rb +25 -0
  29. data/lib/bitcoin/message/network_addr.rb +31 -12
  30. data/lib/bitcoin/message/version.rb +14 -22
  31. data/lib/bitcoin/mnemonic.rb +5 -5
  32. data/lib/bitcoin/network/peer.rb +12 -11
  33. data/lib/bitcoin/network/peer_discovery.rb +3 -1
  34. data/lib/bitcoin/node/cli.rb +14 -10
  35. data/lib/bitcoin/node/spv.rb +1 -1
  36. data/lib/bitcoin/out_point.rb +14 -7
  37. data/lib/bitcoin/payment_code.rb +92 -0
  38. data/lib/bitcoin/psbt.rb +3 -1
  39. data/lib/bitcoin/psbt/input.rb +7 -16
  40. data/lib/bitcoin/psbt/tx.rb +18 -12
  41. data/lib/bitcoin/rpc/bitcoin_core_client.rb +22 -12
  42. data/lib/bitcoin/rpc/request_handler.rb +3 -3
  43. data/lib/bitcoin/script/script.rb +18 -10
  44. data/lib/bitcoin/script/script_interpreter.rb +3 -5
  45. data/lib/bitcoin/secp256k1.rb +1 -0
  46. data/lib/bitcoin/secp256k1/rfc6979.rb +43 -0
  47. data/lib/bitcoin/secp256k1/ruby.rb +4 -35
  48. data/lib/bitcoin/slip39.rb +93 -0
  49. data/lib/bitcoin/slip39/share.rb +122 -0
  50. data/lib/bitcoin/slip39/sss.rb +245 -0
  51. data/lib/bitcoin/slip39/wordlist/english.txt +1024 -0
  52. data/lib/bitcoin/store.rb +2 -1
  53. data/lib/bitcoin/store/chain_entry.rb +1 -0
  54. data/lib/bitcoin/store/db/level_db.rb +2 -2
  55. data/lib/bitcoin/store/utxo_db.rb +226 -0
  56. data/lib/bitcoin/tx.rb +6 -10
  57. data/lib/bitcoin/tx_in.rb +4 -5
  58. data/lib/bitcoin/util.rb +29 -1
  59. data/lib/bitcoin/version.rb +1 -1
  60. data/lib/bitcoin/wallet.rb +1 -0
  61. data/lib/bitcoin/wallet/account.rb +1 -0
  62. data/lib/bitcoin/wallet/base.rb +3 -3
  63. data/lib/bitcoin/wallet/db.rb +1 -1
  64. data/lib/bitcoin/wallet/master_key.rb +1 -0
  65. data/lib/bitcoin/wallet/utxo.rb +37 -0
  66. metadata +45 -26
@@ -1,4 +1,4 @@
1
- require 'leveldb'
1
+ require 'leveldb-native'
2
2
 
3
3
  module Bitcoin
4
4
  module Store
@@ -6,6 +6,7 @@ module Bitcoin
6
6
  autoload :DB, 'bitcoin/store/db'
7
7
  autoload :SPVChain, 'bitcoin/store/spv_chain'
8
8
  autoload :ChainEntry, 'bitcoin/store/chain_entry'
9
+ autoload :UtxoDB, 'bitcoin/store/utxo_db'
9
10
 
10
11
  end
11
12
  end
@@ -3,6 +3,7 @@ module Bitcoin
3
3
 
4
4
  # wrap a block header object with extra data.
5
5
  class ChainEntry
6
+ include Bitcoin::HexConverter
6
7
 
7
8
  attr_reader :header
8
9
  attr_reader :height
@@ -1,4 +1,4 @@
1
- require 'leveldb'
1
+ require 'leveldb-native'
2
2
 
3
3
  module Bitcoin
4
4
  module Store
@@ -12,7 +12,7 @@ module Bitcoin
12
12
  def initialize(path = "#{Bitcoin.base_dir}/db/spv")
13
13
  # @logger = Bitcoin::Logger.create(:debug)
14
14
  FileUtils.mkdir_p(path)
15
- @db = ::LevelDB::DB.new(path)
15
+ @db = ::LevelDBNative::DB.new(path)
16
16
  # logger.debug 'Opened LevelDB successfully.'
17
17
  end
18
18
 
@@ -0,0 +1,226 @@
1
+ require 'leveldb-native'
2
+
3
+ module Bitcoin
4
+ module Store
5
+ class UtxoDB
6
+
7
+ KEY_PREFIX = {
8
+ out_point: 'o', # key: out_point(tx_hash and index), value: Utxo
9
+ script: 's', # key: script_pubkey and out_point(tx_hash and index), value: Utxo
10
+ height: 'h', # key: block_height and out_point, value: Utxo
11
+ tx_hash: 't', # key: tx_hash of transaction, value: [block_height, tx_index]
12
+ block: 'b', # key: block_height and tx_index, value: tx_hash
13
+ tx_payload: 'p', # key: tx_hash, value: Tx
14
+ }
15
+
16
+ attr_reader :level_db, :logger
17
+
18
+ def initialize(path = "#{Bitcoin.base_dir}/db/utxo")
19
+ FileUtils.mkdir_p(path)
20
+ @level_db = ::LevelDBNative::DB.new(path)
21
+ @logger = Bitcoin::Logger.create(:debug)
22
+ end
23
+
24
+ def close
25
+ level_db.close
26
+ end
27
+
28
+ # Save payload of a transaction into db
29
+ #
30
+ # @param [String] tx_hash
31
+ # @param [String] tx_payload
32
+ def save_tx(tx_hash, tx_payload)
33
+ logger.info("UtxoDB#save_tx:#{[tx_hash, tx_payload]}")
34
+ level_db.batch do
35
+ # tx_hash -> [block_height, tx_index]
36
+ key = KEY_PREFIX[:tx_payload] + tx_hash
37
+ level_db.put(key, tx_payload)
38
+ end
39
+ end
40
+
41
+ # Save tx position (block height and index in the block) into db
42
+ # When node receives `header` message, node should call save_tx_position to store block height and its index.
43
+ #
44
+ # @param [String] tx_hash
45
+ # @param [Integer] block_height
46
+ # @param [Integer] tx_index
47
+ def save_tx_position(tx_hash, block_height, tx_index)
48
+ logger.info("UtxoDB#save_tx_position:#{[tx_hash, block_height, tx_index]}")
49
+ level_db.batch do
50
+ # tx_hash -> [block_height, tx_index]
51
+ key = KEY_PREFIX[:tx_hash] + tx_hash
52
+ level_db.put(key, [block_height, tx_index].pack('N2').bth)
53
+
54
+ # block_hash and tx_index -> tx_hash
55
+ key = KEY_PREFIX[:block] + [block_height, tx_index].pack('N2').bth
56
+ level_db.put(key, tx_hash)
57
+ end
58
+ end
59
+
60
+ # Save utxo into db
61
+ #
62
+ # @param [Bitcoin::OutPoint] out_point
63
+ # @param [Double] value
64
+ # @param [Bitcoin::Script] script_pubkey
65
+ # @param [Integer] block_height
66
+ def save_utxo(out_point, value, script_pubkey, block_height=nil)
67
+ logger.info("UtxoDB#save_utxo:#{[out_point, value, script_pubkey, block_height]}")
68
+ level_db.batch do
69
+ utxo = Bitcoin::Wallet::Utxo.new(out_point.tx_hash, out_point.index, value, script_pubkey, block_height)
70
+ payload = utxo.to_payload
71
+
72
+ # out_point
73
+ key = KEY_PREFIX[:out_point] + out_point.to_hex
74
+ return if level_db.contains?(key)
75
+ level_db.put(key, payload)
76
+
77
+ # script_pubkey
78
+ if script_pubkey
79
+ key = KEY_PREFIX[:script] + script_pubkey.to_hex + out_point.to_hex
80
+ level_db.put(key, payload)
81
+ end
82
+
83
+ # height
84
+ unless block_height.nil?
85
+ key = KEY_PREFIX[:height] + [block_height].pack('N').bth + out_point.to_hex
86
+ level_db.put(key, payload)
87
+ end
88
+
89
+ utxo
90
+ end
91
+ end
92
+
93
+ # Get transaction stored via save_tx and save_tx_position
94
+ #
95
+ # @param [string] tx_hash
96
+ # @return [block_height, tx_index, tx_payload]
97
+ def get_tx(tx_hash)
98
+ key = KEY_PREFIX[:tx_hash] + tx_hash
99
+ return [] unless level_db.contains?(key)
100
+ block_height, tx_index = level_db.get(key).htb.unpack('N2')
101
+ key = KEY_PREFIX[:tx_payload] + tx_hash
102
+ tx_payload = level_db.get(key)
103
+ [block_height, tx_index, tx_payload]
104
+ end
105
+
106
+ # Delete utxo from db
107
+ #
108
+ # @param [Bitcoin::Outpoint] out_point
109
+ # @return [Bitcoin::Wallet::Utxo]
110
+ def delete_utxo(out_point)
111
+ level_db.batch do
112
+ # [:out_point]
113
+ key = KEY_PREFIX[:out_point] + out_point.to_hex
114
+ return unless level_db.contains?(key)
115
+ utxo = Bitcoin::Wallet::Utxo.parse_from_payload(level_db.get(key))
116
+ level_db.delete(key)
117
+
118
+ # [:script]
119
+ if utxo.script_pubkey
120
+ key = KEY_PREFIX[:script] + utxo.script_pubkey.to_hex + out_point.to_hex
121
+ level_db.delete(key)
122
+ end
123
+
124
+ if utxo.block_height
125
+ # [:height]
126
+ key = KEY_PREFIX[:height] + [utxo.block_height].pack('N').bth + out_point.to_hex
127
+ level_db.delete(key)
128
+
129
+ # [:block]
130
+ key = KEY_PREFIX[:block] + [utxo.block_height, utxo.index].pack('N2').bth
131
+ level_db.delete(key)
132
+ end
133
+
134
+ # handles both [:tx_hash] and [:tx_payload]
135
+ if utxo.tx_hash
136
+ key = KEY_PREFIX[:tx_hash] + utxo.tx_hash
137
+ level_db.delete(key)
138
+
139
+ key = KEY_PREFIX[:tx_payload] + utxo.tx_hash
140
+ level_db.delete(key)
141
+ end
142
+
143
+ utxo
144
+ end
145
+ end
146
+
147
+ # Get utxo of the specified out point
148
+ #
149
+ # @param [Bitcoin::Outpoint] out_point
150
+ # @return [Bitcoin::Wallet::Utxo]
151
+ def get_utxo(out_point)
152
+ level_db.batch do
153
+ key = KEY_PREFIX[:out_point] + out_point.to_hex
154
+ return unless level_db.contains?(key)
155
+ return Bitcoin::Wallet::Utxo.parse_from_payload(level_db.get(key))
156
+ end
157
+ end
158
+
159
+ # return [Bitcoin::Wallet::Utxo ...]
160
+ def list_unspent(current_block_height: 9999999, min: 0, max: 9999999, addresses: nil)
161
+ if addresses
162
+ list_unspent_by_addresses(current_block_height, min: min, max: max, addresses: addresses)
163
+ else
164
+ list_unspent_by_block_height(current_block_height, min: min, max: max)
165
+ end
166
+ end
167
+
168
+ # @param [Bitcoin::Wallet::Account]
169
+ # return [Bitcoin::Wallet::Utxo ...]
170
+ def list_unspent_in_account(account, current_block_height: 9999999, min: 0, max: 9999999)
171
+ return [] unless account
172
+
173
+ script_pubkeys = case account.purpose
174
+ when Bitcoin::Wallet::Account::PURPOSE_TYPE[:legacy]
175
+ account.watch_targets.map { |t| Bitcoin::Script.to_p2pkh(t).to_hex }
176
+ when Bitcoin::Wallet::Account::PURPOSE_TYPE[:nested_witness]
177
+ account.watch_targets.map { |t| Bitcoin::Script.to_p2wpkh(t).to_p2sh.to_hex }
178
+ when Bitcoin::Wallet::Account::PURPOSE_TYPE[:native_segwit]
179
+ account.watch_targets.map { |t| Bitcoin::Script.to_p2wpkh(t).to_hex }
180
+ end
181
+ list_unspent_by_script_pubkeys(current_block_height, min: min, max: max, script_pubkeys: script_pubkeys)
182
+ end
183
+
184
+ # @param [Bitcoin::Wallet::Account]
185
+ # return [Bitcoin::Wallet::Utxo ...]
186
+ def get_balance(account, current_block_height: 9999999, min: 0, max: 9999999)
187
+ list_unspent_in_account(account, current_block_height: current_block_height, min: min, max: max).sum { |u| u.value }
188
+ end
189
+
190
+ private
191
+
192
+ def utxos_between(from, to)
193
+ level_db.each(from: from, to: to).map { |k, v| Bitcoin::Wallet::Utxo.parse_from_payload(v) }
194
+ end
195
+
196
+ class ::Array
197
+ def with_height(min, max)
198
+ select { |u| u.block_height >= min && u.block_height <= max }
199
+ end
200
+ end
201
+
202
+ def list_unspent_by_block_height(current_block_height, min: 0, max: 9999999)
203
+ max_height = [current_block_height - min, 0].max
204
+ min_height = [current_block_height - max, 0].max
205
+ from = KEY_PREFIX[:height] + [min_height].pack('N').bth + '000000000000000000000000000000000000000000000000000000000000000000000000'
206
+ to = KEY_PREFIX[:height] + [max_height].pack('N').bth + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
207
+ utxos_between(from, to)
208
+ end
209
+
210
+ def list_unspent_by_addresses(current_block_height, min: 0, max: 9999999, addresses: [])
211
+ script_pubkeys = addresses.map { |a| Bitcoin::Script.parse_from_addr(a).to_hex }
212
+ list_unspent_by_script_pubkeys(current_block_height, min: min, max: max, script_pubkeys: script_pubkeys)
213
+ end
214
+
215
+ def list_unspent_by_script_pubkeys(current_block_height, min: 0, max: 9999999, script_pubkeys: [])
216
+ max_height = current_block_height - min
217
+ min_height = current_block_height - max
218
+ script_pubkeys.map do |key|
219
+ from = KEY_PREFIX[:script] + key + '000000000000000000000000000000000000000000000000000000000000000000000000'
220
+ to = KEY_PREFIX[:script] + key + 'ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'
221
+ utxos_between(from, to).with_height(min_height, max_height)
222
+ end.flatten
223
+ end
224
+ end
225
+ end
226
+ end
@@ -6,6 +6,8 @@ module Bitcoin
6
6
  # Transaction class
7
7
  class Tx
8
8
 
9
+ include Bitcoin::HexConverter
10
+
9
11
  MAX_STANDARD_VERSION = 2
10
12
 
11
13
  # The maximum weight for transactions we're willing to relay/mine
@@ -70,7 +72,7 @@ module Bitcoin
70
72
  end
71
73
 
72
74
  def hash
73
- to_payload.bth.to_i(16)
75
+ to_hex.to_i(16)
74
76
  end
75
77
 
76
78
  def tx_hash
@@ -104,12 +106,6 @@ module Bitcoin
104
106
  witness? ? serialize_witness_format : serialize_old_format
105
107
  end
106
108
 
107
- # convert tx to hex format.
108
- # @return [String] tx with hex format.
109
- def to_hex
110
- to_payload.bth
111
- end
112
-
113
109
  def coinbase_tx?
114
110
  inputs.length == 1 && inputs.first.coinbase?
115
111
  end
@@ -202,7 +198,7 @@ module Bitcoin
202
198
  raise ArgumentError, 'script_pubkey must be specified.' unless output_script
203
199
  raise ArgumentError, 'unsupported sig version specified.' unless SIG_VERSION.include?(sig_version)
204
200
 
205
- if sig_version == :witness_v0 || Bitcoin.chain_params.fork_chain?
201
+ if sig_version == :witness_v0
206
202
  raise ArgumentError, 'amount must be specified.' unless amount
207
203
  sighash_for_witness(input_index, output_script, hash_type, amount, skip_separator_index)
208
204
  else
@@ -225,7 +221,7 @@ module Bitcoin
225
221
  script_pubkey = redeem_script if redeem_script.p2wpkh?
226
222
  end
227
223
 
228
- if has_witness || Bitcoin.chain_params.fork_chain?
224
+ if has_witness
229
225
  verify_input_sig_for_witness(input_index, script_pubkey, amount, flags)
230
226
  else
231
227
  verify_input_sig_for_legacy(input_index, script_pubkey, flags)
@@ -310,7 +306,7 @@ module Bitcoin
310
306
  if (hash_type & SIGHASH_TYPE[:anyonecanpay]) != 0
311
307
  hash_prevouts = hash_sequence ="\x00".ljust(32, "\x00")
312
308
  end
313
- hash_type |= (Bitcoin.chain_params.fork_id << 8) if Bitcoin.chain_params.fork_chain?
309
+
314
310
  buf = [ [version].pack('V'), hash_prevouts, hash_sequence, outpoint,
315
311
  script_code ,amount, nsequence, hash_outputs, [@lock_time, hash_type].pack('VV')].join
316
312
  Bitcoin.double_sha256(buf)
@@ -37,11 +37,10 @@ module Bitcoin
37
37
  hash, index = buf.read(36).unpack('a32V')
38
38
  i.out_point = OutPoint.new(hash.bth, index)
39
39
  sig_length = Bitcoin.unpack_var_int_from_io(buf)
40
- sig = buf.read(sig_length)
41
- if i.coinbase?
42
- i.script_sig.chunks[0] = sig
40
+ if sig_length == 0
41
+ i.script_sig = Bitcoin::Script.new
43
42
  else
44
- i.script_sig = Script.parse_from_payload(sig)
43
+ i.script_sig = Script.parse_from_payload(buf.read(sig_length))
45
44
  end
46
45
  i.sequence = buf.read(4).unpack('V').first
47
46
  i
@@ -78,7 +77,7 @@ module Bitcoin
78
77
  # return previous output hash (not txid)
79
78
  def prev_hash
80
79
  return nil unless out_point
81
- out_point.hash
80
+ out_point.tx_hash
82
81
  end
83
82
 
84
83
  end
@@ -61,7 +61,7 @@ module Bitcoin
61
61
  end
62
62
 
63
63
  def pack_boolean(b)
64
- b ? [0xFF].pack('C') : [0x00].pack('C')
64
+ b ? [0x01].pack('C') : [0x00].pack('C')
65
65
  end
66
66
 
67
67
  def unpack_boolean(payload)
@@ -82,6 +82,15 @@ module Bitcoin
82
82
  byte.unpack('b*').first
83
83
  end
84
84
 
85
+ # padding zero to the left of binary string until bytesize.
86
+ # @param [String] binary string
87
+ # @param [Integer] bytesize total bytesize.
88
+ # @return [String] padded binary string.
89
+ def padding_zero(binary, bytesize)
90
+ return binary unless binary.bytesize < bytesize
91
+ ('00' * (bytesize - binary.bytesize)).htb + binary
92
+ end
93
+
85
94
  # generate sha256-ripemd160 hash for value
86
95
  def hash160(hex)
87
96
  Digest::RMD160.hexdigest(Digest::SHA256.digest(hex.htb))
@@ -119,6 +128,25 @@ module Bitcoin
119
128
  OpenSSL::HMAC.digest(DIGEST_NAME_SHA256, key, data)
120
129
  end
121
130
 
131
+ # check whether +addr+ is valid address.
132
+ # @param [String] addr an address
133
+ # @return [Boolean] if valid address return true, otherwise false.
134
+ def valid_address?(addr)
135
+ begin
136
+ Bitcoin::Script.parse_from_addr(addr)
137
+ true
138
+ rescue Exception
139
+ false
140
+ end
141
+ end
142
+
122
143
  end
123
144
 
145
+ module HexConverter
146
+
147
+ def to_hex
148
+ to_payload.bth
149
+ end
150
+
151
+ end
124
152
  end
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.2.9"
2
+ VERSION = "0.5.0"
3
3
  end
@@ -4,5 +4,6 @@ module Bitcoin
4
4
  autoload :Account, 'bitcoin/wallet/account'
5
5
  autoload :DB, 'bitcoin/wallet/db'
6
6
  autoload :MasterKey, 'bitcoin/wallet/master_key'
7
+ autoload :Utxo, 'bitcoin/wallet/utxo'
7
8
  end
8
9
  end
@@ -3,6 +3,7 @@ module Bitcoin
3
3
 
4
4
  # the account in BIP-44
5
5
  class Account
6
+ include Bitcoin::HexConverter
6
7
 
7
8
  PURPOSE_TYPE = {legacy: 44, nested_witness: 49, native_segwit: 84}
8
9
 
@@ -1,4 +1,4 @@
1
- require 'leveldb'
1
+ require 'leveldb-native'
2
2
  module Bitcoin
3
3
  module Wallet
4
4
 
@@ -20,14 +20,14 @@ module Bitcoin
20
20
  # @param [String] wallet_id new wallet id.
21
21
  # @param [String] path_prefix wallet file path prefix.
22
22
  # @return [Bitcoin::Wallet::Base] the wallet
23
- def self.create(wallet_id = 1, path_prefix = default_path_prefix)
23
+ def self.create(wallet_id = 1, path_prefix = default_path_prefix, purpose = Account::PURPOSE_TYPE[:native_segwit])
24
24
  raise ArgumentError, "wallet_id : #{wallet_id} already exist." if self.exist?(wallet_id, path_prefix)
25
25
  w = self.new(wallet_id, path_prefix)
26
26
  # generate seed
27
27
  raise RuntimeError, 'the seed already exist.' if w.db.registered_master?
28
28
  master = Bitcoin::Wallet::MasterKey.generate
29
29
  w.db.register_master_key(master)
30
- w.create_account('Default')
30
+ w.create_account(purpose, 'Default')
31
31
  w
32
32
  end
33
33
 
@@ -15,7 +15,7 @@ module Bitcoin
15
15
 
16
16
  def initialize(path = "#{Bitcoin.base_dir}/db/wallet")
17
17
  FileUtils.mkdir_p(path)
18
- @level_db = ::LevelDB::DB.new(path)
18
+ @level_db = ::LevelDBNative::DB.new(path)
19
19
  end
20
20
 
21
21
  # close database