bitcoinrb 0.1.3 → 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
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