bitcoinrb 0.1.3 → 0.1.4

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -0
  3. data/README.md +31 -9
  4. data/bitcoinrb.conf.sample +0 -0
  5. data/bitcoinrb.gemspec +6 -2
  6. data/exe/bitcoinrb-cli +2 -2
  7. data/lib/bitcoin/block_header.rb +9 -2
  8. data/lib/bitcoin/chainparams/regtest.yml +1 -1
  9. data/lib/bitcoin/constants.rb +3 -0
  10. data/lib/bitcoin/key.rb +70 -5
  11. data/lib/bitcoin/logger.rb +25 -1
  12. data/lib/bitcoin/message/addr.rb +0 -39
  13. data/lib/bitcoin/message/headers.rb +1 -0
  14. data/lib/bitcoin/message/inventory.rb +4 -0
  15. data/lib/bitcoin/message/network_addr.rb +44 -0
  16. data/lib/bitcoin/message/version.rb +8 -3
  17. data/lib/bitcoin/message.rb +12 -0
  18. data/lib/bitcoin/mnemonic.rb +2 -2
  19. data/lib/bitcoin/network/connection.rb +14 -1
  20. data/lib/bitcoin/network/message_handler.rb +52 -19
  21. data/lib/bitcoin/network/peer.rb +142 -5
  22. data/lib/bitcoin/network/peer_discovery.rb +16 -8
  23. data/lib/bitcoin/network/pool.rb +39 -14
  24. data/lib/bitcoin/network.rb +0 -1
  25. data/lib/bitcoin/node/cli.rb +66 -0
  26. data/lib/bitcoin/node/configuration.rb +37 -0
  27. data/lib/bitcoin/node/spv.rb +13 -3
  28. data/lib/bitcoin/node.rb +2 -1
  29. data/lib/bitcoin/rpc/http_server.rb +56 -0
  30. data/lib/bitcoin/rpc/request_handler.rb +84 -0
  31. data/lib/bitcoin/rpc.rb +6 -0
  32. data/lib/bitcoin/script/multisig.rb +92 -0
  33. data/lib/bitcoin/script/script.rb +17 -7
  34. data/lib/bitcoin/script/script_interpreter.rb +2 -38
  35. data/lib/bitcoin/store/chain_entry.rb +64 -0
  36. data/lib/bitcoin/store/db/level_db.rb +101 -0
  37. data/lib/bitcoin/store/db.rb +9 -0
  38. data/lib/bitcoin/store/spv_chain.rb +96 -0
  39. data/lib/bitcoin/store.rb +5 -1
  40. data/lib/bitcoin/tx.rb +6 -2
  41. data/lib/bitcoin/version.rb +1 -1
  42. data/lib/bitcoin/wallet/account.rb +82 -0
  43. data/lib/bitcoin/wallet/base.rb +84 -0
  44. data/lib/bitcoin/wallet/db.rb +57 -0
  45. data/lib/bitcoin/wallet/master_key.rb +100 -0
  46. data/lib/bitcoin/wallet.rb +8 -0
  47. data/lib/bitcoin.rb +4 -0
  48. data/lib/openassets/payload.rb +6 -2
  49. metadata +70 -13
  50. data/lib/bitcoin/node/spv_block_chain.rb +0 -15
  51. data/lib/bitcoin/store/spv_block_store.rb +0 -11
@@ -0,0 +1,84 @@
1
+ module Bitcoin
2
+ module RPC
3
+
4
+ # RPC server's request handler.
5
+ module RequestHandler
6
+
7
+ # Returns an object containing various state info regarding blockchain processing.
8
+ def getblockchaininfo
9
+ h = {}
10
+ h[:chain] = Bitcoin.chain_params.network
11
+ best_block = node.chain.latest_block
12
+ h[:headers] = best_block.height
13
+ h[:bestblockhash] = best_block.hash
14
+ h[:chainwork] = best_block.header.work
15
+ h[:mediantime] = node.chain.mtp(best_block.hash)
16
+ h
17
+ end
18
+
19
+ # shutdown node
20
+ def stop
21
+ node.shutdown
22
+ end
23
+
24
+ # get block header information.
25
+ def getblockheader(hash, verbose)
26
+ entry = node.chain.find_entry_by_hash(hash)
27
+ if verbose
28
+ {
29
+ hash: hash,
30
+ height: entry.height,
31
+ version: entry.header.version,
32
+ versionHex: entry.header.version.to_s(16),
33
+ merkleroot: entry.header.merkle_root,
34
+ time: entry.header.time,
35
+ mediantime: node.chain.mtp(hash),
36
+ nonce: entry.header.nonce,
37
+ bits: entry.header.bits.to_s(16),
38
+ previousblockhash: entry.prev_hash,
39
+ nextblockhash: node.chain.next_hash(hash)
40
+ }
41
+ else
42
+ entry.header.to_payload.bth
43
+ end
44
+ end
45
+
46
+ # Returns connected peer information.
47
+ def getpeerinfo
48
+ node.pool.peers.map do |peer|
49
+ local_addr = peer.remote_version.remote_addr[0..peer.remote_version.remote_addr.rindex(':')] + '18333'
50
+ {
51
+ id: peer.id,
52
+ addr: "#{peer.host}:#{peer.port}",
53
+ addrlocal: local_addr,
54
+ services: peer.remote_version.services.to_s(16).rjust(16, '0'),
55
+ relaytxes: peer.remote_version.relay,
56
+ lastsend: peer.last_send,
57
+ lastrecv: peer.last_recv,
58
+ bytessent: peer.bytes_sent,
59
+ bytesrecv: peer.bytes_recv,
60
+ conntime: peer.conn_time,
61
+ pingtime: peer.ping_time,
62
+ minping: peer.min_ping,
63
+ version: peer.remote_version.version,
64
+ subver: peer.remote_version.user_agent,
65
+ inbound: !peer.outbound?,
66
+ startingheight: peer.remote_version.start_height,
67
+ best_hash: peer.best_hash,
68
+ best_height: peer.best_height
69
+ }
70
+ end
71
+ end
72
+
73
+ # broadcast transaction
74
+ def sendrawtransaction(hex_tx)
75
+ tx = Bitcoin::Tx.parse_from_payload(hex_tx.htb)
76
+ # TODO check wether tx is valid
77
+ node.broadcast(tx)
78
+ tx.txid
79
+ end
80
+
81
+ end
82
+
83
+ end
84
+ end
@@ -0,0 +1,6 @@
1
+ module Bitcoin
2
+ module RPC
3
+ autoload :HttpServer, 'bitcoin/rpc/http_server'
4
+ autoload :RequestHandler, 'bitcoin/rpc/request_handler'
5
+ end
6
+ end
@@ -0,0 +1,92 @@
1
+ module Bitcoin
2
+
3
+ # utility for multisig
4
+ module Multisig
5
+ include Bitcoin::Opcodes
6
+
7
+ def self.prefix
8
+ [OP_0].pack("C*")
9
+ end
10
+
11
+ # generate input script sig spending a multisig output script.
12
+ # returns a raw binary script sig of the form:
13
+ # OP_0 <sig> [<sig> ...]
14
+ # @param [[String]] array of signatures
15
+ # @return [String] script_sig for multisig
16
+ def self.to_multisig_script_sig(*sigs)
17
+ hash_type = sigs.last.is_a?(Numeric) ? sigs.pop : SIGHASH_TYPE[:all]
18
+ sigs.reverse.inject(prefix) { |joined, sig| add_sig_to_multisig_script_sig(sig, joined, hash_type) }
19
+ end
20
+
21
+ # take a multisig script sig (or p2sh multisig script sig) and add
22
+ # another signature to it after the OP_0. Used to sign a tx by
23
+ # multiple parties. Signatures must be in the same order as the
24
+ # pubkeys in the output script being redeemed.
25
+ def self.add_sig_to_multisig_script_sig(sig_to_add, script_sig, hash_type = SIGHASH_TYPE[:all])
26
+ signature = sig_to_add + [hash_type].pack("C*")
27
+ offset = script_sig.empty? ? 0 : 1
28
+ script_sig.insert(offset, Bitcoin::Script.pack_pushdata(signature))
29
+ end
30
+
31
+ # generate input script sig spending a p2sh-multisig output script.
32
+ # returns a raw binary script sig of the form:
33
+ # OP_0 <sig> [<sig> ...] <redeem_script>
34
+ # @param [Script] redeem_script
35
+ # @param [[String]] array of signatures
36
+ # @return [String] script_sig for multisig
37
+ def self.to_p2sh_multisig_script_sig(redeem_script, *sigs)
38
+ to_multisig_script_sig(*sigs.flatten) + Bitcoin::Script.pack_pushdata(redeem_script)
39
+ end
40
+
41
+ # Sort signatures in the given +script_sig+ according to the order of pubkeys in
42
+ # the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
43
+ # @param [String] signature for multisig.
44
+ # @param [String] sig_hash to be signed.
45
+ # @return [String] sorted sig_hash.
46
+ def self.sort_p2sh_multisig_signatures(script_sig, sig_hash)
47
+ script = Bitcoin::Script.parse_from_payload(script_sig)
48
+ redeem_script = Bitcoin::Script.parse_from_payload(script.chunks[-1].pushed_data)
49
+ pubkeys = redeem_script.get_multisig_pubkeys
50
+
51
+ # find the pubkey for each signature by trying to verify it
52
+ sigs = Hash[script.chunks[1...-1].map.with_index do |sig, idx|
53
+ sig = sig.pushed_data
54
+ pubkey = pubkeys.map do |key|
55
+ Bitcoin::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
56
+ end.compact.first
57
+ raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
58
+ [pubkey, sig]
59
+ end]
60
+
61
+ prefix + pubkeys.map { |k| sigs[k] ? Bitcoin::Script.pack_pushdata(sigs[k]) : nil }.join +
62
+ Bitcoin::Script.pack_pushdata(script.chunks[-1].pushed_data)
63
+ end
64
+
65
+ def self.add_sig_to_multisig_script_witness(sig_to_add, script_witness, hash_type = SIGHASH_TYPE[:all])
66
+ signature = sig_to_add + [hash_type].pack("C*")
67
+ script_witness.stack << signature
68
+ end
69
+
70
+ # Sort signatures in the given +script_witness+ according to the order of pubkeys in
71
+ # the redeem script. Also needs the +sig_hash+ to match signatures to pubkeys.
72
+ # @param [ScriptWitness] script_witness for multisig.
73
+ # @param [String] sig_hash to be signed.
74
+ def self.sort_witness_multisig_signatures(script_witness, sig_hash)
75
+ redeem_script = Bitcoin::Script.parse_from_payload(script_witness.stack[-1])
76
+ pubkeys = redeem_script.get_multisig_pubkeys
77
+ sigs = Hash[script_witness.stack[1...-1].map.with_index do |sig, idx|
78
+ pubkey = pubkeys.map do |key|
79
+ Bitcoin::Key.new(pubkey: key.bth).verify(sig, sig_hash) ? key : nil
80
+ end.compact.first
81
+ raise "Key for signature ##{idx} not found in redeem script!" unless pubkey
82
+ [pubkey, sig]
83
+ end]
84
+ script_witness.stack.clear
85
+ script_witness.stack << ''
86
+ pubkeys.each do |pubkey|
87
+ script_witness.stack << sigs[pubkey] if sigs[pubkey]
88
+ end
89
+ script_witness.stack << redeem_script.to_payload
90
+ end
91
+ end
92
+ end
@@ -35,6 +35,11 @@ module Bitcoin
35
35
  Script.new << OP_HASH160 << to_hash160 << OP_EQUAL
36
36
  end
37
37
 
38
+ def get_multisig_pubkeys
39
+ num = Bitcoin::Opcodes.opcode_to_small_int(chunks[-2].bth.to_i(16))
40
+ (1..num).map{ |i| chunks[i].pushed_data }
41
+ end
42
+
38
43
  # generate m of n multisig script
39
44
  # @param [String] m the number of signatures required for multisig
40
45
  # @param [Array] pubkeys array of public keys that compose multisig
@@ -43,6 +48,7 @@ module Bitcoin
43
48
  new << m << pubkeys << pubkeys.size << OP_CHECKMULTISIG
44
49
  end
45
50
 
51
+
46
52
  # generate p2wsh script for +redeem_script+
47
53
  # @param [Script] redeem_script target redeem script
48
54
  # @param [Script] p2wsh script
@@ -109,9 +115,8 @@ module Bitcoin
109
115
 
110
116
  def to_addr
111
117
  return p2pkh_addr if p2pkh?
112
- return p2wpkh_addr if p2wpkh?
113
- return p2wsh_addr if p2wsh?
114
118
  return p2sh_addr if p2sh?
119
+ return bech32_addr if witness_program?
115
120
  end
116
121
 
117
122
  # check whether standard script.
@@ -258,11 +263,16 @@ module Bitcoin
258
263
 
259
264
  def to_s
260
265
  chunks.map { |c|
261
- if c.pushdata?
262
- v = Opcodes.opcode_to_small_int(c.ord)
263
- v ? v : c.pushed_data.bth
264
- else
265
- Opcodes.opcode_to_name(c.ord)
266
+ case c
267
+ when Integer
268
+ opcode_to_name(c)
269
+ when String
270
+ if c.pushdata?
271
+ v = Opcodes.opcode_to_small_int(c.ord)
272
+ v ? v : c.pushed_data.bth
273
+ else
274
+ Opcodes.opcode_to_name(c.ord)
275
+ end
266
276
  end
267
277
  }.join(' ')
268
278
  end
@@ -589,7 +589,7 @@ module Bitcoin
589
589
 
590
590
  def check_signature_encoding(sig)
591
591
  return true if sig.size.zero?
592
- if (flag?(SCRIPT_VERIFY_DERSIG) || flag?(SCRIPT_VERIFY_LOW_S) || flag?(SCRIPT_VERIFY_STRICTENC)) && !valid_signature_encoding?(sig)
592
+ if (flag?(SCRIPT_VERIFY_DERSIG) || flag?(SCRIPT_VERIFY_LOW_S) || flag?(SCRIPT_VERIFY_STRICTENC)) && !Key.valid_signature_encoding?(sig.htb)
593
593
  return set_error(SCRIPT_ERR_SIG_DER)
594
594
  elsif flag?(SCRIPT_VERIFY_LOW_S) && !low_der_signature?(sig)
595
595
  return false
@@ -599,44 +599,8 @@ module Bitcoin
599
599
  true
600
600
  end
601
601
 
602
- # check +sig+ (hex) is correct der encoding.
603
- # This function is consensus-critical since BIP66.
604
- def valid_signature_encoding?(signature)
605
- sig = signature.htb
606
- return false if sig.bytesize < 9 || sig.bytesize > 73 # Minimum and maximum size check
607
-
608
- s = sig.unpack('C*')
609
-
610
- return false if s[0] != 0x30 || s[1] != s.size - 3 # A signature is of type 0x30 (compound). Make sure the length covers the entire signature.
611
-
612
- len_r = s[3]
613
- return false if 5 + len_r >= s.size # Make sure the length of the S element is still inside the signature.
614
-
615
- len_s = s[5 + len_r]
616
- return false unless len_r + len_s + 7 == s.size #Verify that the length of the signature matches the sum of the length of the elements.
617
-
618
- return false unless s[2] == 0x02 # Check whether the R element is an integer.
619
-
620
- return false if len_r == 0 # Zero-length integers are not allowed for R.
621
-
622
- return false unless s[4] & 0x80 == 0 # Negative numbers are not allowed for R.
623
-
624
- # Null bytes at the start of R are not allowed, unless R would otherwise be interpreted as a negative number.
625
- return false if len_r > 1 && (s[4] == 0x00) && (s[5] & 0x80 == 0)
626
-
627
- return false unless s[len_r + 4] == 0x02 # Check whether the S element is an integer.
628
-
629
- return false if len_s == 0 # Zero-length integers are not allowed for S.
630
- return false unless (s[len_r + 6] & 0x80) == 0 # Negative numbers are not allowed for S.
631
-
632
- # Null bytes at the start of S are not allowed, unless S would otherwise be interpreted as a negative number.
633
- return false if len_s > 1 && (s[len_r + 6] == 0x00) && (s[len_r + 7] & 0x80 == 0)
634
-
635
- true
636
- end
637
-
638
602
  def low_der_signature?(sig)
639
- return set_error(SCRIPT_ERR_SIG_DER) unless valid_signature_encoding?(sig)
603
+ return set_error(SCRIPT_ERR_SIG_DER) unless Key.valid_signature_encoding?(sig.htb)
640
604
  return set_error(SCRIPT_ERR_SIG_HIGH_S) unless Key.low_signature?(sig.htb)
641
605
  true
642
606
  end
@@ -0,0 +1,64 @@
1
+ module Bitcoin
2
+ module Store
3
+
4
+ # wrap a block header object with extra data.
5
+ class ChainEntry
6
+
7
+ attr_reader :header
8
+ attr_reader :height
9
+
10
+ # @param [Bitcoin::BlockHeader] header a block header.
11
+ # @param [Integer] height a block height.
12
+ def initialize(header, height)
13
+ @header = header
14
+ @height = height
15
+ end
16
+
17
+ # get database key
18
+ def key
19
+ Bitcoin::Store::KEY_PREFIX[:entry] + header.hash
20
+ end
21
+
22
+ # block hash
23
+ def hash
24
+ header.hash
25
+ end
26
+
27
+ # previous block hash
28
+ def prev_hash
29
+ header.prev_hash
30
+ end
31
+
32
+ # whether genesis block
33
+ def genesis?
34
+ Bitcoin.chain_params.genesis_block.header == header
35
+ end
36
+
37
+ # @param [String] payload a payload with binary format.
38
+ def self.parse_from_payload(payload)
39
+ buf = StringIO.new(payload)
40
+ len = Bitcoin.unpack_var_int_from_io(buf)
41
+ height = buf.read(len).reverse.bth.to_i(16)
42
+ new(Bitcoin::BlockHeader.parse_from_payload(buf.read(80)), height)
43
+ end
44
+
45
+ # build next block +StoredBlock+ instance.
46
+ # @param [Bitcoin::BlockHeader] next_block a next block candidate header.
47
+ # @return [Bitcoin::Store::ChainEntry] a next stored block (not saved).
48
+ def build_next_block(next_block)
49
+ ChainEntry.new(next_block, height + 1)
50
+ end
51
+
52
+ # generate payload
53
+ def to_payload
54
+ height_value = height.to_s(16)
55
+ height_value = '0' + height_value if height_value.length.odd?
56
+ height_value = height_value.htb.reverse
57
+ Bitcoin.pack_var_int(height_value.bytesize) + height_value + header.to_payload
58
+ end
59
+
60
+ end
61
+
62
+ end
63
+
64
+ end
@@ -0,0 +1,101 @@
1
+ require 'leveldb'
2
+
3
+ module Bitcoin
4
+ module Store
5
+ module DB
6
+
7
+ class LevelDB
8
+
9
+ attr_reader :db
10
+ attr_reader :logger
11
+
12
+ def initialize(path = "#{Bitcoin.base_dir}/db/spv")
13
+ # @logger = Bitcoin::Logger.create(:debug)
14
+ FileUtils.mkdir_p(path)
15
+ @db = ::LevelDB::DB.new(path)
16
+ # logger.debug 'Opened LevelDB successfully.'
17
+ end
18
+
19
+ # put data into LevelDB.
20
+ # @param [Object] key a key.
21
+ # @param [Object] value a value.
22
+ def put(key, value)
23
+ # logger.debug "put #{key} data"
24
+ db.put(key, value)
25
+ end
26
+
27
+ # get value from specified key.
28
+ # @param [Object] key a key.
29
+ # @return[Object] the stored value.
30
+ def get(key)
31
+ db.get(key)
32
+ end
33
+
34
+ # get best block hash.
35
+ def best_hash
36
+ db.get(KEY_PREFIX[:best])
37
+ end
38
+
39
+ # delete specified key data.
40
+ def delete(key)
41
+ db.delete(key)
42
+ end
43
+
44
+ # get block hash specified +height+
45
+ def get_hash_from_height(height)
46
+ db.get(height_key(height))
47
+ end
48
+
49
+ # get next block hash specified +hash+
50
+ def next_hash(hash)
51
+ db.get(KEY_PREFIX[:next] + hash)
52
+ end
53
+
54
+ # get entry payload
55
+ # @param [String] hash the hash with hex format.
56
+ # @return [String] the ChainEntry payload.
57
+ def get_entry_payload_from_hash(hash)
58
+ db.get(KEY_PREFIX[:entry] + hash)
59
+ end
60
+
61
+ def save_entry(entry)
62
+ db.batch do
63
+ db.put(entry.key ,entry.to_payload)
64
+ db.put(height_key(entry.height), entry.hash)
65
+ connect_entry(entry)
66
+ end
67
+ end
68
+
69
+ def close
70
+ db.close
71
+ end
72
+
73
+ private
74
+
75
+ # generate height key
76
+ def height_key(height)
77
+ height = height.to_s(16)
78
+ height = '0' + height if height.bytesize.odd?
79
+ KEY_PREFIX[:height] + height.htb.reverse.bth
80
+ end
81
+
82
+ def connect_entry(entry)
83
+ unless entry.genesis?
84
+ tip_block = Bitcoin::Store::ChainEntry.parse_from_payload(get_entry_payload_from_hash(best_hash))
85
+ unless tip_block.hash == entry.prev_hash
86
+ raise "entry(#{entry.hash}) does not reference current best block hash(#{tip_block.hash})"
87
+ end
88
+ unless tip_block.height + 1 == entry.height
89
+ raise "block height is small than current best block."
90
+ end
91
+ end
92
+
93
+
94
+ db.put(KEY_PREFIX[:best], entry.hash)
95
+ db.put(KEY_PREFIX[:next] + entry.prev_hash, entry.hash)
96
+ end
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,9 @@
1
+ module Bitcoin
2
+ module Store
3
+
4
+ module DB
5
+ autoload :LevelDB, 'bitcoin/store/db/level_db'
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,96 @@
1
+ module Bitcoin
2
+
3
+ module Store
4
+
5
+ KEY_PREFIX = {
6
+ entry: 'e', # key: block hash, value: Bitcoin::Store::ChainEntry payload
7
+ height: 'h', # key: block height, value: block hash.
8
+ best: 'B', # value: best block hash.
9
+ next: 'n' # key: block hash, value: A hash of the next block of the specified hash
10
+ }
11
+
12
+ class SPVChain
13
+
14
+ attr_reader :db
15
+ attr_reader :logger
16
+
17
+ def initialize(db = Bitcoin::Store::DB::LevelDB.new)
18
+ @db = db # TODO multiple db switch
19
+ @logger = Bitcoin::Logger.create(:debug)
20
+ initialize_block
21
+ end
22
+
23
+ # get latest block in the store.
24
+ # @return[Bitcoin::Store::ChainEntry]
25
+ def latest_block
26
+ hash = db.best_hash
27
+ return nil unless hash
28
+ find_entry_by_hash(hash)
29
+ end
30
+
31
+ # find block entry with the specified height.
32
+ def find_entry_by_height(height)
33
+ find_entry_by_hash(db.get_hash_from_height(height))
34
+ end
35
+
36
+ # find block entry with the specified hash
37
+ def find_entry_by_hash(hash)
38
+ payload = db.get_entry_payload_from_hash(hash)
39
+ ChainEntry.parse_from_payload(payload)
40
+ end
41
+
42
+ # append block header to chain.
43
+ # @param [Bitcoin::BlockHeader] header a block header.
44
+ # @return [Bitcoin::Store::ChainEntry] appended block header entry.
45
+ def append_header(header)
46
+ logger.debug("append header #{header.hash}")
47
+ raise "this header is invalid. #{header.hash}" unless header.valid?
48
+ best_block = latest_block
49
+ current_height = best_block.height
50
+ if best_block.hash == header.prev_hash
51
+ entry = Bitcoin::Store::ChainEntry.new(header, current_height + 1)
52
+ db.save_entry(entry)
53
+ entry
54
+ else
55
+ # TODO implements recovery process
56
+ raise "header's previous hash(#{header.prev_hash}) does not match current best block's(#{best_block.hash})."
57
+ end
58
+ end
59
+
60
+ # get next block hash for specified +hash+
61
+ # @param [String] hash the block hash
62
+ # @return [String] the next block hash. If it does not exist yet, return nil.
63
+ def next_hash(hash)
64
+ db.next_hash(hash)
65
+ end
66
+
67
+ # get median time past for specified block +hash+
68
+ # @param [String] hash the block hash.
69
+ # @return [Integer] the median time past value.
70
+ def mtp(hash)
71
+ time = []
72
+ Bitcoin::MEDIAN_TIME_SPAN.times do
73
+ entry = find_entry_by_hash(hash)
74
+ time << entry.header.time
75
+ hash = entry.header.prev_hash
76
+ end
77
+ time.sort!
78
+ time[time.size / 2]
79
+ end
80
+
81
+ private
82
+
83
+ # if database is empty, put genesis block.
84
+ def initialize_block
85
+ unless latest_block
86
+ block = Bitcoin.chain_params.genesis_block
87
+ genesis = ChainEntry.new(block.header, 0)
88
+ db.save_entry(genesis)
89
+ end
90
+ end
91
+
92
+ end
93
+
94
+ end
95
+
96
+ end
data/lib/bitcoin/store.rb CHANGED
@@ -1,7 +1,11 @@
1
+ require 'leveldb'
2
+
1
3
  module Bitcoin
2
4
  module Store
3
5
 
4
- autoload :SPVBlockStore, 'bitcoin/store/spv_block_store'
6
+ autoload :DB, 'bitcoin/store/db'
7
+ autoload :SPVChain, 'bitcoin/store/spv_chain'
8
+ autoload :ChainEntry, 'bitcoin/store/chain_entry'
5
9
 
6
10
  end
7
11
  end
data/lib/bitcoin/tx.rb CHANGED
@@ -35,8 +35,12 @@ module Bitcoin
35
35
  if in_count.zero?
36
36
  tx.marker = 0
37
37
  tx.flag = buf.read(1).unpack('c').first
38
- in_count = Bitcoin.unpack_var_int_from_io(buf)
39
- witness = true
38
+ if tx.flag.zero?
39
+ buf.pos -= 1
40
+ else
41
+ in_count = Bitcoin.unpack_var_int_from_io(buf)
42
+ witness = true
43
+ end
40
44
  end
41
45
 
42
46
  in_count.times do
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.1.3"
2
+ VERSION = "0.1.4"
3
3
  end
@@ -0,0 +1,82 @@
1
+ module Bitcoin
2
+ module Wallet
3
+
4
+ # the account in BIP-44
5
+ class Account
6
+
7
+ PURPOSE_TYPE = {legacy: 44, nested_witness: 49}
8
+
9
+ attr_reader :purpose # either 44 or 49
10
+ attr_reader :index # BIP-44 index
11
+ attr_reader :name # account name
12
+ attr_accessor :receive_depth # receive address depth
13
+ attr_accessor :change_depth # change address depth
14
+ attr_accessor :lookahead
15
+ attr_accessor :wallet
16
+
17
+ def initialize(purpose = PURPOSE_TYPE[:legacy], index = 0, name = '')
18
+ @purpose = purpose
19
+ @index = index
20
+ @name = name
21
+ @receive_depth = 0
22
+ @change_depth = 0
23
+ @lookahead = 10
24
+ end
25
+
26
+ def self.parse_from_payload(payload)
27
+ name, payload = Bitcoin.unpack_var_string(payload)
28
+ name = name.force_encoding('utf-8')
29
+ purpose, index, receive_depth, change_depth, lookahead = payload.unpack('I*')
30
+ a = Account.new(purpose, index, name)
31
+ a.receive_depth = receive_depth
32
+ a.change_depth = change_depth
33
+ a.lookahead = lookahead
34
+ a
35
+ end
36
+
37
+ def to_payload
38
+ payload = Bitcoin.pack_var_string(name.unpack('H*').first.htb)
39
+ payload << [purpose, index, receive_depth, change_depth, lookahead].pack('I*')
40
+ payload
41
+ end
42
+
43
+ # whether support witness
44
+ def witness?
45
+ purpose == PURPOSE_TYPE[:nested_witness]
46
+ end
47
+
48
+ def init
49
+ lookahead.times do |index|
50
+ derive_receive(index)
51
+ derive_change(index)
52
+ end
53
+ @index = wallet.accounts.size
54
+ save
55
+ end
56
+
57
+ # derive receive key
58
+ def derive_receive(index)
59
+ derive(0, index)
60
+ end
61
+
62
+ # derive change key
63
+ def derive_change(index)
64
+ derive(1, index)
65
+ end
66
+
67
+ # save this account payload to database.
68
+ def save
69
+ wallet.db.save_account(self)
70
+ end
71
+
72
+ private
73
+
74
+ # derive key
75
+ def derive(branch, index)
76
+
77
+ end
78
+
79
+ end
80
+
81
+ end
82
+ end