bitcoinrb 0.1.6 → 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/lib/bitcoin/block.rb +5 -6
- data/lib/bitcoin/block_header.rb +10 -5
- data/lib/bitcoin/bloom_filter.rb +20 -13
- data/lib/bitcoin/chain_params.rb +9 -1
- data/lib/bitcoin/chainparams/mainnet.yml +8 -0
- data/lib/bitcoin/chainparams/regtest.yml +8 -0
- data/lib/bitcoin/chainparams/testnet.yml +8 -0
- data/lib/bitcoin/ext_key.rb +115 -38
- data/lib/bitcoin/key.rb +12 -2
- data/lib/bitcoin/merkle_tree.rb +1 -2
- data/lib/bitcoin/message/filter_load.rb +4 -8
- data/lib/bitcoin/network/peer.rb +1 -5
- data/lib/bitcoin/network/pool.rb +6 -3
- data/lib/bitcoin/node/cli.rb +15 -0
- data/lib/bitcoin/node/spv.rb +12 -9
- data/lib/bitcoin/rpc/request_handler.rb +32 -9
- data/lib/bitcoin/store/db/level_db.rb +1 -1
- data/lib/bitcoin/store/spv_chain.rb +5 -3
- data/lib/bitcoin/tx.rb +10 -2
- data/lib/bitcoin/version.rb +1 -1
- data/lib/bitcoin/wallet/account.rb +71 -25
- data/lib/bitcoin/wallet/base.rb +52 -7
- data/lib/bitcoin/wallet/db.rb +2 -1
- data/lib/bitcoin/wallet/master_key.rb +18 -2
- data/lib/bitcoin.rb +6 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3d66a90af2b70a6494a6b38d0602a4442a0173e27294cec5b389149b77bc7360
|
4
|
+
data.tar.gz: 1b830903242397fece09064f51a150b9716ef1d06409f597ca60c6ee76e80ebc
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 397ecea38a38e72c3a66d6ca18461a41af2ecfa747eb71c9193c9c7a1629c96e49a8338348a2bd88f3be0e01184bb5148dd6e53e968c83dae8c3da4d2105e253
|
7
|
+
data.tar.gz: bf3fca048c69f4939f1c4cae0da6c9be03e0650dd18f271d5cac045d4a30528ef3e3268e3290c6f03cadb6d2dbd06cd6ac0405e11e4400359085bfbd36d55f46
|
data/LICENSE.txt
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
The MIT License (MIT)
|
2
2
|
|
3
|
-
Copyright (c) 2017 HAW International, Inc.
|
3
|
+
Copyright (c) 2017-2018 HAW International, Inc.
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
data/lib/bitcoin/block.rb
CHANGED
@@ -33,7 +33,7 @@ module Bitcoin
|
|
33
33
|
|
34
34
|
# calculate merkle root from tx list.
|
35
35
|
def calculate_merkle_root
|
36
|
-
Bitcoin::MerkleTree.build_from_leaf(transactions.map(&:
|
36
|
+
Bitcoin::MerkleTree.build_from_leaf(transactions.map(&:hash)).merkle_root
|
37
37
|
end
|
38
38
|
|
39
39
|
# check the witness commitment in coinbase tx matches witness commitment calculated from tx list.
|
@@ -43,12 +43,11 @@ module Bitcoin
|
|
43
43
|
|
44
44
|
# calculate witness commitment from tx list.
|
45
45
|
def calculate_witness_commitment
|
46
|
-
|
47
|
-
|
46
|
+
witness_hashes = [COINBASE_WTXID]
|
47
|
+
witness_hashes += (transactions[1..-1].map(&:witness_hash))
|
48
48
|
reserved_value = transactions[0].inputs[0].script_witness.stack.map(&:bth).join
|
49
|
-
root_hash = Bitcoin::MerkleTree.build_from_leaf(
|
50
|
-
|
51
|
-
[reserved_value + root_hash].pack('H*').reverse )).bth
|
49
|
+
root_hash = Bitcoin::MerkleTree.build_from_leaf(witness_hashes).merkle_root
|
50
|
+
Bitcoin.double_sha256([root_hash + reserved_value].pack('H*')).bth
|
52
51
|
end
|
53
52
|
|
54
53
|
# return this block height. block height is included in coinbase.
|
data/lib/bitcoin/block_header.rb
CHANGED
@@ -21,11 +21,11 @@ module Bitcoin
|
|
21
21
|
|
22
22
|
def self.parse_from_payload(payload)
|
23
23
|
version, prev_hash, merkle_root, time, bits, nonce = payload.unpack('Va32a32VVV')
|
24
|
-
new(version, prev_hash.
|
24
|
+
new(version, prev_hash.bth, merkle_root.bth, time, bits, nonce)
|
25
25
|
end
|
26
26
|
|
27
27
|
def to_payload
|
28
|
-
[version, prev_hash.htb
|
28
|
+
[version, prev_hash.htb, merkle_root.htb, time, bits, nonce].pack('Va32a32VVV')
|
29
29
|
end
|
30
30
|
|
31
31
|
# compute difficulty target from bits.
|
@@ -36,11 +36,16 @@ module Bitcoin
|
|
36
36
|
(mantissa * 2 ** (8 * (exponent - 3)))
|
37
37
|
end
|
38
38
|
|
39
|
-
# block hash
|
39
|
+
# block hash(little endian)
|
40
40
|
def hash
|
41
41
|
calc_hash
|
42
42
|
end
|
43
43
|
|
44
|
+
# block hash(big endian)
|
45
|
+
def block_id
|
46
|
+
hash.rhex
|
47
|
+
end
|
48
|
+
|
44
49
|
# evaluate block header
|
45
50
|
def valid?
|
46
51
|
valid_pow? && valid_timestamp?
|
@@ -48,7 +53,7 @@ module Bitcoin
|
|
48
53
|
|
49
54
|
# evaluate valid proof of work.
|
50
55
|
def valid_pow?
|
51
|
-
|
56
|
+
block_id.hex < difficulty_target
|
52
57
|
end
|
53
58
|
|
54
59
|
# evaluate valid timestamp.
|
@@ -72,7 +77,7 @@ module Bitcoin
|
|
72
77
|
private
|
73
78
|
|
74
79
|
def calc_hash
|
75
|
-
Bitcoin.double_sha256(to_payload).
|
80
|
+
Bitcoin.double_sha256(to_payload).bth
|
76
81
|
end
|
77
82
|
|
78
83
|
end
|
data/lib/bitcoin/bloom_filter.rb
CHANGED
@@ -7,24 +7,31 @@ module Bitcoin
|
|
7
7
|
MAX_BLOOM_FILTER_SIZE = 36_000 # bytes
|
8
8
|
MAX_HASH_FUNCS = 50
|
9
9
|
|
10
|
-
attr_reader :hash_funcs, :tweak
|
10
|
+
attr_reader :filter, :hash_funcs, :tweak
|
11
11
|
|
12
|
+
def initialize(filter, hash_funcs, tweak)
|
13
|
+
@filter = filter
|
14
|
+
@hash_funcs = hash_funcs
|
15
|
+
@tweak = tweak
|
16
|
+
end
|
17
|
+
|
18
|
+
# Create a new bloom filter.
|
12
19
|
# @param [Integer] elements_length the number of elements
|
13
20
|
# @param [Float] fp_rate the false positive rate chosen by the client
|
14
21
|
# @param [Integer] tweak A random value to add to the seed value in the hash function used by the bloom filter
|
15
|
-
def
|
22
|
+
def self.create_filter(elements_length, fp_rate, tweak = 0)
|
16
23
|
# The size S of the filter in bytes is given by (-1 / pow(log(2), 2) * N * log(P)) / 8
|
17
24
|
len = [[(-elements_length * Math.log(fp_rate) / (LN2_SQUARED * 8)).to_i, MAX_BLOOM_FILTER_SIZE].min, 1].max
|
18
|
-
|
25
|
+
filter = Array.new(len, 0)
|
19
26
|
# The number of hash functions required is given by S * 8 / N * log(2)
|
20
|
-
|
21
|
-
|
27
|
+
hash_funcs = [[(filter.size * 8 * LN2 / elements_length).to_i, MAX_HASH_FUNCS].min, 1].max
|
28
|
+
BloomFilter.new(filter, hash_funcs, tweak)
|
22
29
|
end
|
23
30
|
|
24
31
|
# @param [String] data The data element to add to the current filter.
|
25
32
|
def add(data)
|
26
33
|
return if full?
|
27
|
-
|
34
|
+
hash_funcs.times do |i|
|
28
35
|
hash = to_hash(data, i)
|
29
36
|
set_bit(hash)
|
30
37
|
end
|
@@ -35,7 +42,7 @@ module Bitcoin
|
|
35
42
|
# @return [Boolean] true if the given data matches the filter
|
36
43
|
def contains?(data)
|
37
44
|
return true if full?
|
38
|
-
|
45
|
+
hash_funcs.times do |i|
|
39
46
|
hash = to_hash(data, i)
|
40
47
|
return false unless check_bit(hash)
|
41
48
|
end
|
@@ -43,29 +50,29 @@ module Bitcoin
|
|
43
50
|
end
|
44
51
|
|
45
52
|
def clear
|
46
|
-
|
53
|
+
filter.fill(0)
|
47
54
|
@full = false
|
48
55
|
end
|
49
56
|
|
50
57
|
def to_a
|
51
|
-
|
58
|
+
filter
|
52
59
|
end
|
53
60
|
|
54
61
|
private
|
55
62
|
def to_hash(data, i)
|
56
|
-
MurmurHash3::V32.str_hash(data, (i * 0xfba4c795 +
|
63
|
+
MurmurHash3::V32.str_hash(data, (i * 0xfba4c795 + tweak) & 0xffffffff) % (filter.length * 8)
|
57
64
|
end
|
58
65
|
|
59
66
|
def set_bit(data)
|
60
|
-
|
67
|
+
filter[data >> 3] |= (1 << (7 & data))
|
61
68
|
end
|
62
69
|
|
63
70
|
def check_bit(data)
|
64
|
-
|
71
|
+
filter[data >> 3] & (1 << (7 & data)) != 0
|
65
72
|
end
|
66
73
|
|
67
74
|
def full?
|
68
|
-
@full |=
|
75
|
+
@full |= filter.all? {|byte| byte == 0xff}
|
69
76
|
end
|
70
77
|
end
|
71
78
|
end
|
data/lib/bitcoin/chain_params.rb
CHANGED
@@ -14,6 +14,14 @@ module Bitcoin
|
|
14
14
|
attr_reader :privkey_version
|
15
15
|
attr_reader :extended_privkey_version
|
16
16
|
attr_reader :extended_pubkey_version
|
17
|
+
attr_reader :bip49_pubkey_p2wpkh_p2sh_version
|
18
|
+
attr_reader :bip49_privkey_p2wpkh_p2sh_version
|
19
|
+
attr_reader :bip49_pubkey_p2wsh_p2sh_version
|
20
|
+
attr_reader :bip49_privkey_p2wsh_p2sh_version
|
21
|
+
attr_reader :bip84_pubkey_p2wpkh_version
|
22
|
+
attr_reader :bip84_privkey_p2wpkh_version
|
23
|
+
attr_reader :bip84_pubkey_p2wsh_version
|
24
|
+
attr_reader :bip84_privkey_p2wsh_version
|
17
25
|
attr_reader :default_port
|
18
26
|
attr_reader :protocol_version
|
19
27
|
attr_reader :retarget_interval
|
@@ -58,7 +66,7 @@ module Bitcoin
|
|
58
66
|
|
59
67
|
def genesis_block
|
60
68
|
header = Bitcoin::BlockHeader.new(
|
61
|
-
genesis['version'], genesis['prev_hash'], genesis['merkle_root'],
|
69
|
+
genesis['version'], genesis['prev_hash'].rhex, genesis['merkle_root'].rhex,
|
62
70
|
genesis['time'], genesis['bits'], genesis['nonce'])
|
63
71
|
Bitcoin::Block.new(header)
|
64
72
|
end
|
@@ -8,6 +8,14 @@ bech32_hrp: 'bc'
|
|
8
8
|
privkey_version: "80"
|
9
9
|
extended_privkey_version: "0488ade4"
|
10
10
|
extended_pubkey_version: "0488b21e"
|
11
|
+
bip49_pubkey_p2wpkh_p2sh_version: "049d7cb2"
|
12
|
+
bip49_pubkey_p2wsh_p2sh_version: "0295b43f"
|
13
|
+
bip49_privkey_p2wpkh_p2sh_version: "049d7878"
|
14
|
+
bip49_privkey_p2wsh_p2sh_version: "0295b005"
|
15
|
+
bip84_pubkey_p2wpkh_version: "04b24746"
|
16
|
+
bip84_pubkey_p2wsh_version: "02aa7ed3"
|
17
|
+
bip84_privkey_p2wpkh_version: "04b2430c"
|
18
|
+
bip84_privkey_p2wsh_version: "02aa7a99"
|
11
19
|
default_port: 8333
|
12
20
|
protocol_version: 70013
|
13
21
|
retarget_interval: 2016
|
@@ -8,6 +8,14 @@ bech32_hrp: 'bcrt'
|
|
8
8
|
privkey_version: "ef"
|
9
9
|
extended_privkey_version: "04358394"
|
10
10
|
extended_pubkey_version: "043587cf"
|
11
|
+
bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
|
12
|
+
bip49_pubkey_p2wsh_p2sh_version: "024285ef"
|
13
|
+
bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
|
14
|
+
bip49_privkey_p2wsh_p2sh_version: "024285b5"
|
15
|
+
bip84_pubkey_p2wpkh_version: "045f1cf6"
|
16
|
+
bip84_pubkey_p2wsh_version: "02575483"
|
17
|
+
bip84_privkey_p2wpkh_version: "045f18bc"
|
18
|
+
bip84_privkey_p2wsh_version: "02575048"
|
11
19
|
default_port: 18444
|
12
20
|
protocol_version: 70013
|
13
21
|
retarget_interval: 2016
|
@@ -8,6 +8,14 @@ bech32_hrp: 'tb'
|
|
8
8
|
privkey_version: "ef"
|
9
9
|
extended_privkey_version: "04358394"
|
10
10
|
extended_pubkey_version: "043587cf"
|
11
|
+
bip49_pubkey_p2wpkh_p2sh_version: "044a5262"
|
12
|
+
bip49_pubkey_p2wsh_p2sh_version: "024285ef"
|
13
|
+
bip49_privkey_p2wpkh_p2sh_version: "044a4e28"
|
14
|
+
bip49_privkey_p2wsh_p2sh_version: "024285b5"
|
15
|
+
bip84_pubkey_p2wpkh_version: "045f1cf6"
|
16
|
+
bip84_pubkey_p2wsh_version: "02575483"
|
17
|
+
bip84_privkey_p2wpkh_version: "045f18bc"
|
18
|
+
bip84_privkey_p2wsh_version: "02575048"
|
11
19
|
default_port: 18333
|
12
20
|
protocol_version: 70013
|
13
21
|
retarget_interval: 2016
|
data/lib/bitcoin/ext_key.rb
CHANGED
@@ -6,10 +6,11 @@ module Bitcoin
|
|
6
6
|
# BIP32 Extended private key
|
7
7
|
class ExtKey
|
8
8
|
|
9
|
+
attr_accessor :ver
|
9
10
|
attr_accessor :depth
|
10
11
|
attr_accessor :number
|
11
12
|
attr_accessor :chain_code
|
12
|
-
attr_accessor :key
|
13
|
+
attr_accessor :key # Bitcoin::Key
|
13
14
|
attr_accessor :parent_fingerprint
|
14
15
|
|
15
16
|
# generate master key from seed.
|
@@ -34,13 +35,14 @@ module Bitcoin
|
|
34
35
|
k.parent_fingerprint = parent_fingerprint
|
35
36
|
k.chain_code = chain_code
|
36
37
|
k.pubkey = key.pubkey
|
38
|
+
k.ver = priv_ver_to_pub_ver
|
37
39
|
k
|
38
40
|
end
|
39
41
|
|
40
42
|
# serialize extended private key
|
41
43
|
def to_payload
|
42
|
-
|
43
|
-
|
44
|
+
version.htb << [depth].pack('C') << parent_fingerprint.htb <<
|
45
|
+
[number].pack('N') << chain_code << [0x00].pack('C') << key.priv_key.htb
|
44
46
|
end
|
45
47
|
|
46
48
|
# Base58 encoded extended private key
|
@@ -66,12 +68,7 @@ module Bitcoin
|
|
66
68
|
|
67
69
|
# get address
|
68
70
|
def addr
|
69
|
-
|
70
|
-
end
|
71
|
-
|
72
|
-
# get segwit p2wpkh address
|
73
|
-
def segwit_addr
|
74
|
-
ext_pubkey.segwit_addr
|
71
|
+
ext_pubkey.addr
|
75
72
|
end
|
76
73
|
|
77
74
|
# get key identifier
|
@@ -107,27 +104,72 @@ module Bitcoin
|
|
107
104
|
raise 'invalid key ' if child_priv >= CURVE_ORDER
|
108
105
|
new_key.key = Bitcoin::Key.new(priv_key: child_priv.to_s(16).rjust(64, '0'))
|
109
106
|
new_key.chain_code = l[32..-1]
|
107
|
+
new_key.ver = version
|
110
108
|
new_key
|
111
109
|
end
|
112
110
|
|
113
|
-
#
|
114
|
-
def
|
115
|
-
|
111
|
+
# get version bytes using serialization format
|
112
|
+
def version
|
113
|
+
return ExtKey.version_from_purpose(number) if depth == 1
|
114
|
+
ver ? ver : Bitcoin.chain_params.extended_privkey_version
|
115
|
+
end
|
116
|
+
|
117
|
+
def self.parse_from_payload(payload)
|
118
|
+
buf = StringIO.new(payload)
|
116
119
|
ext_key = ExtKey.new
|
117
|
-
|
118
|
-
|
119
|
-
ext_key.
|
120
|
-
ext_key.
|
121
|
-
ext_key.
|
122
|
-
|
123
|
-
|
120
|
+
ext_key.ver = buf.read(4).bth # version
|
121
|
+
raise 'An unsupported version byte was specified.' unless ExtKey.support_version?(ext_key.ver)
|
122
|
+
ext_key.depth = buf.read(1).unpack('C').first
|
123
|
+
ext_key.parent_fingerprint = buf.read(4).bth
|
124
|
+
ext_key.number = buf.read(4).unpack('N').first
|
125
|
+
ext_key.chain_code = buf.read(32)
|
126
|
+
buf.read(1) # 0x00
|
127
|
+
ext_key.key = Bitcoin::Key.new(priv_key: buf.read(32).bth)
|
124
128
|
ext_key
|
125
129
|
end
|
126
130
|
|
131
|
+
# import private key from Base58 private key address
|
132
|
+
def self.from_base58(address)
|
133
|
+
ExtKey.parse_from_payload(Base58.decode(address).htb)
|
134
|
+
end
|
135
|
+
|
136
|
+
# get version bytes from purpose' value.
|
137
|
+
def self.version_from_purpose(purpose)
|
138
|
+
v = purpose - 2**31
|
139
|
+
case v
|
140
|
+
when 49
|
141
|
+
Bitcoin.chain_params.bip49_privkey_p2wpkh_p2sh_version
|
142
|
+
when 84
|
143
|
+
Bitcoin.chain_params.bip84_privkey_p2wpkh_version
|
144
|
+
else
|
145
|
+
Bitcoin.chain_params.extended_privkey_version
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# check whether +version+ is supported version bytes.
|
150
|
+
def self.support_version?(version)
|
151
|
+
p = Bitcoin.chain_params
|
152
|
+
[p.bip49_privkey_p2wpkh_p2sh_version, p.bip84_privkey_p2wpkh_version, p.extended_privkey_version].include?(version)
|
153
|
+
end
|
154
|
+
|
155
|
+
# convert privkey version to pubkey version
|
156
|
+
def priv_ver_to_pub_ver
|
157
|
+
case version
|
158
|
+
when Bitcoin.chain_params.bip49_privkey_p2wpkh_p2sh_version
|
159
|
+
Bitcoin.chain_params.bip49_pubkey_p2wpkh_p2sh_version
|
160
|
+
when Bitcoin.chain_params.bip84_privkey_p2wpkh_version
|
161
|
+
Bitcoin.chain_params.bip84_pubkey_p2wpkh_version
|
162
|
+
else
|
163
|
+
Bitcoin.chain_params.extended_pubkey_version
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
127
167
|
end
|
128
168
|
|
129
169
|
# BIP-32 Extended public key
|
130
170
|
class ExtPubkey
|
171
|
+
|
172
|
+
attr_accessor :ver
|
131
173
|
attr_accessor :depth
|
132
174
|
attr_accessor :number
|
133
175
|
attr_accessor :chain_code
|
@@ -136,7 +178,7 @@ module Bitcoin
|
|
136
178
|
|
137
179
|
# serialize extended pubkey
|
138
180
|
def to_payload
|
139
|
-
|
181
|
+
version.htb << [depth].pack('C') <<
|
140
182
|
parent_fingerprint.htb << [number].pack('N') << chain_code << pub.htb
|
141
183
|
end
|
142
184
|
|
@@ -150,17 +192,20 @@ module Bitcoin
|
|
150
192
|
|
151
193
|
# get address
|
152
194
|
def addr
|
153
|
-
|
195
|
+
case version
|
196
|
+
when Bitcoin.chain_params.bip49_pubkey_p2wpkh_p2sh_version
|
197
|
+
key.to_nested_p2wpkh
|
198
|
+
when Bitcoin.chain_params.bip84_pubkey_p2wpkh_version
|
199
|
+
key.to_p2wpkh
|
200
|
+
else
|
201
|
+
key.to_p2pkh
|
202
|
+
end
|
154
203
|
end
|
155
204
|
|
156
|
-
# get
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
segwit_addr = Bech32::SegwitAddr.new
|
161
|
-
segwit_addr.hrp = Bitcoin.chain_params.address_version == '00' ? 'bc' : 'tb'
|
162
|
-
segwit_addr.script_pubkey = p2wpkh
|
163
|
-
segwit_addr.addr
|
205
|
+
# get key object
|
206
|
+
# @return [Bitcoin::Key]
|
207
|
+
def key
|
208
|
+
Bitcoin::Key.new(pubkey: pubkey)
|
164
209
|
end
|
165
210
|
|
166
211
|
# get key identifier
|
@@ -200,21 +245,53 @@ module Bitcoin
|
|
200
245
|
p2 = Bitcoin::Key.new(pubkey: pubkey).to_point
|
201
246
|
new_key.pubkey = ECDSA::Format::PointOctetString.encode(p1 + p2, compression: true).bth
|
202
247
|
new_key.chain_code = l[32..-1]
|
248
|
+
new_key.ver = version
|
203
249
|
new_key
|
204
250
|
end
|
205
251
|
|
206
|
-
#
|
207
|
-
def
|
208
|
-
|
252
|
+
# get version bytes using serialization format
|
253
|
+
def version
|
254
|
+
return ExtPubkey.version_from_purpose(number) if depth == 1
|
255
|
+
ver ? ver : Bitcoin.chain_params.extended_pubkey_version
|
256
|
+
end
|
257
|
+
|
258
|
+
def self.parse_from_payload(payload)
|
259
|
+
buf = StringIO.new(payload)
|
209
260
|
ext_pubkey = ExtPubkey.new
|
210
|
-
|
211
|
-
|
212
|
-
ext_pubkey.
|
213
|
-
ext_pubkey.
|
214
|
-
ext_pubkey.
|
215
|
-
ext_pubkey.
|
261
|
+
ext_pubkey.ver = buf.read(4).bth # version
|
262
|
+
raise 'An unsupported version byte was specified.' unless ExtPubkey.support_version?(ext_pubkey.ver)
|
263
|
+
ext_pubkey.depth = buf.read(1).unpack('C').first
|
264
|
+
ext_pubkey.parent_fingerprint = buf.read(4).bth
|
265
|
+
ext_pubkey.number = buf.read(4).unpack('N').first
|
266
|
+
ext_pubkey.chain_code = buf.read(32)
|
267
|
+
ext_pubkey.pubkey = buf.read(33).bth
|
216
268
|
ext_pubkey
|
217
269
|
end
|
270
|
+
|
271
|
+
# import pub key from Base58 private key address
|
272
|
+
def self.from_base58(address)
|
273
|
+
ExtPubkey.parse_from_payload(Base58.decode(address).htb)
|
274
|
+
end
|
275
|
+
|
276
|
+
# get version bytes from purpose' value.
|
277
|
+
def self.version_from_purpose(purpose)
|
278
|
+
v = purpose - 2**31
|
279
|
+
case v
|
280
|
+
when 49
|
281
|
+
Bitcoin.chain_params.bip49_pubkey_p2wpkh_p2sh_version
|
282
|
+
when 84
|
283
|
+
Bitcoin.chain_params.bip84_pubkey_p2wpkh_version
|
284
|
+
else
|
285
|
+
Bitcoin.chain_params.extended_pubkey_version
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
# check whether +version+ is supported version bytes.
|
290
|
+
def self.support_version?(version)
|
291
|
+
p = Bitcoin.chain_params
|
292
|
+
[p.bip49_pubkey_p2wpkh_p2sh_version, p.bip84_pubkey_p2wpkh_version, p.extended_pubkey_version].include?(version)
|
293
|
+
end
|
294
|
+
|
218
295
|
end
|
219
296
|
|
220
297
|
end
|
data/lib/bitcoin/key.rb
CHANGED
@@ -87,14 +87,24 @@ module Bitcoin
|
|
87
87
|
end
|
88
88
|
end
|
89
89
|
|
90
|
+
# get hash160 public key.
|
91
|
+
def hash160
|
92
|
+
Bitcoin.hash160(pubkey)
|
93
|
+
end
|
94
|
+
|
90
95
|
# get pay to pubkey hash address
|
91
96
|
def to_p2pkh
|
92
|
-
Bitcoin::Script.to_p2pkh(
|
97
|
+
Bitcoin::Script.to_p2pkh(hash160).to_addr
|
93
98
|
end
|
94
99
|
|
95
100
|
# get pay to witness pubkey hash address
|
96
101
|
def to_p2wpkh
|
97
|
-
Bitcoin::Script.to_p2wpkh(
|
102
|
+
Bitcoin::Script.to_p2wpkh(hash160).to_addr
|
103
|
+
end
|
104
|
+
|
105
|
+
# get p2wpkh address nested in p2sh.
|
106
|
+
def to_nested_p2wpkh
|
107
|
+
Bitcoin::Script.to_p2wpkh(hash160).to_p2sh.to_addr
|
98
108
|
end
|
99
109
|
|
100
110
|
def compressed?
|
data/lib/bitcoin/merkle_tree.rb
CHANGED
@@ -78,8 +78,7 @@ module Bitcoin
|
|
78
78
|
def hash
|
79
79
|
return @hash if @hash
|
80
80
|
self.right = left.dup unless right
|
81
|
-
|
82
|
-
[right.hash + left.hash].pack('H*').reverse )).reverse.bth
|
81
|
+
Bitcoin.double_sha256([left.hash + right.hash].pack('H*')).bth
|
83
82
|
end
|
84
83
|
|
85
84
|
def root?
|
@@ -11,15 +11,11 @@ module Bitcoin
|
|
11
11
|
BLOOM_UPDATE_ALL = 1
|
12
12
|
BLOOM_UPDATE_P2PUBKEY_ONLY = 2
|
13
13
|
|
14
|
-
attr_accessor :filter
|
15
|
-
attr_accessor :func_count
|
16
|
-
attr_accessor :tweak
|
14
|
+
attr_accessor :filter
|
17
15
|
attr_accessor :flag
|
18
16
|
|
19
|
-
def initialize(filter,
|
17
|
+
def initialize(filter, flag = BLOOM_UPDATE_ALL)
|
20
18
|
@filter = filter
|
21
|
-
@func_count = func_count
|
22
|
-
@tweak = tweak
|
23
19
|
@flag = flag
|
24
20
|
end
|
25
21
|
|
@@ -30,11 +26,11 @@ module Bitcoin
|
|
30
26
|
func_count = buf.read(4).unpack('V').first
|
31
27
|
tweak = buf.read(4).unpack('V').first
|
32
28
|
flag = buf.read(1).unpack('C').first
|
33
|
-
new(filter, func_count, tweak, flag)
|
29
|
+
FilterLoad.new(Bitcoin::BloomFilter.new(filter, func_count, tweak), flag)
|
34
30
|
end
|
35
31
|
|
36
32
|
def to_payload
|
37
|
-
Bitcoin.pack_var_int(filter.size) << filter.pack('C*') << [
|
33
|
+
Bitcoin.pack_var_int(filter.filter.size) << filter.filter.pack('C*') << [filter.hash_funcs, filter.tweak, flag].pack('VVC')
|
38
34
|
end
|
39
35
|
|
40
36
|
end
|
data/lib/bitcoin/network/peer.rb
CHANGED
@@ -190,11 +190,7 @@ module Bitcoin
|
|
190
190
|
# send filterload message.
|
191
191
|
def send_filter_load(bloom)
|
192
192
|
filter_load = Bitcoin::Message::FilterLoad.new(
|
193
|
-
|
194
|
-
bloom.hash_funcs,
|
195
|
-
bloom.tweak,
|
196
|
-
Bitcoin::Message::FilterLoad::BLOOM_UPDATE_ALL
|
197
|
-
)
|
193
|
+
bloom, Bitcoin::Message::FilterLoad::BLOOM_UPDATE_ALL)
|
198
194
|
conn.send_message(filter_load)
|
199
195
|
end
|
200
196
|
|
data/lib/bitcoin/network/pool.rb
CHANGED
@@ -14,13 +14,15 @@ module Bitcoin
|
|
14
14
|
|
15
15
|
attr_reader :peers # active peers
|
16
16
|
attr_reader :pending_peers # currently connecting peer
|
17
|
+
attr_reader :node
|
17
18
|
attr_reader :chain
|
18
19
|
attr_reader :max_outbound
|
19
20
|
attr_reader :logger
|
20
21
|
attr_reader :peer_discovery
|
21
22
|
attr_accessor :started
|
22
23
|
|
23
|
-
def initialize(chain, configuration)
|
24
|
+
def initialize(node, chain, configuration)
|
25
|
+
@node = node
|
24
26
|
@peers = []
|
25
27
|
@pending_peers = []
|
26
28
|
@max_outbound = MAX_OUTBOUND_CONNECTIONS
|
@@ -59,6 +61,7 @@ module Bitcoin
|
|
59
61
|
end
|
60
62
|
peers << peer
|
61
63
|
pending_peers.delete(peer)
|
64
|
+
filter_load(peer) if node.wallet
|
62
65
|
end
|
63
66
|
|
64
67
|
# terminate peers.
|
@@ -75,8 +78,8 @@ module Bitcoin
|
|
75
78
|
end
|
76
79
|
|
77
80
|
# new bloom filter.
|
78
|
-
def filter_load(
|
79
|
-
|
81
|
+
def filter_load(peer)
|
82
|
+
peer.send_filter_load(node.bloom)
|
80
83
|
end
|
81
84
|
|
82
85
|
# add element to bloom filter.
|
data/lib/bitcoin/node/cli.rb
CHANGED
@@ -50,6 +50,21 @@ module Bitcoin
|
|
50
50
|
request('getwalletinfo')
|
51
51
|
end
|
52
52
|
|
53
|
+
desc 'listaccounts', '[WIP]Returns Object that has account names as keys, account balances as values.'
|
54
|
+
def listaccounts
|
55
|
+
request('listaccounts')
|
56
|
+
end
|
57
|
+
|
58
|
+
desc 'encryptwallet "passphrase"', 'Encrypts the wallet with "passphrase". This is for first time encryption.After this, any calls that interact with private keys such as sending or signing will require the passphrase to be set prior the making these calls.'
|
59
|
+
def encryptwallet(passhphrase)
|
60
|
+
request('encryptwallet', passhphrase)
|
61
|
+
end
|
62
|
+
|
63
|
+
desc 'getnewaddress "account"', 'Returns a new Bitcoin address for receiving payments.'
|
64
|
+
def getnewaddress(account)
|
65
|
+
request('getnewaddress', account)
|
66
|
+
end
|
67
|
+
|
53
68
|
private
|
54
69
|
|
55
70
|
def config
|
data/lib/bitcoin/node/spv.rb
CHANGED
@@ -11,17 +11,17 @@ module Bitcoin
|
|
11
11
|
attr_reader :configuration
|
12
12
|
attr_accessor :server
|
13
13
|
attr_accessor :wallet
|
14
|
+
attr_accessor :bloom
|
14
15
|
|
15
16
|
def initialize(configuration)
|
16
17
|
@chain = Bitcoin::Store::SPVChain.new
|
17
18
|
@configuration = configuration
|
18
|
-
@pool = Bitcoin::Network::Pool.new(@chain, @configuration)
|
19
|
+
@pool = Bitcoin::Network::Pool.new(self, @chain, @configuration)
|
19
20
|
@logger = Bitcoin::Logger.create(:debug)
|
20
21
|
@running = false
|
21
22
|
@wallet = Bitcoin::Wallet::Base.current_wallet
|
22
23
|
# TODO : optimize bloom filter parameters
|
23
|
-
|
24
|
-
@bloom = Bitcoin::BloomFilter.new(512, 0.01)
|
24
|
+
setup_filter
|
25
25
|
end
|
26
26
|
|
27
27
|
# open the node.
|
@@ -48,15 +48,10 @@ module Bitcoin
|
|
48
48
|
logger.debug "broadcast tx: #{tx.to_payload.bth}"
|
49
49
|
end
|
50
50
|
|
51
|
-
# new bloom filter.
|
52
|
-
def filter_load
|
53
|
-
pool.filter_load(@bloom)
|
54
|
-
end
|
55
|
-
|
56
51
|
# add filter element to bloom filter.
|
57
52
|
# [String] element. the hex string of txid, public key, public key hash or outpoint.
|
58
53
|
def filter_add(element)
|
59
|
-
|
54
|
+
bloom.add(element)
|
60
55
|
pool.filter_add(element)
|
61
56
|
end
|
62
57
|
|
@@ -64,6 +59,14 @@ module Bitcoin
|
|
64
59
|
def filter_clear
|
65
60
|
pool.filter_clear
|
66
61
|
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def setup_filter
|
66
|
+
@bloom = Bitcoin::BloomFilter.create_filter(512, 0.01)
|
67
|
+
wallet.watch_targets.each{|t|bloom.add(t.htb.reverse)} if wallet
|
68
|
+
end
|
69
|
+
|
67
70
|
end
|
68
71
|
end
|
69
72
|
end
|
@@ -10,7 +10,7 @@ module Bitcoin
|
|
10
10
|
h[:chain] = Bitcoin.chain_params.network
|
11
11
|
best_block = node.chain.latest_block
|
12
12
|
h[:headers] = best_block.height
|
13
|
-
h[:bestblockhash] = best_block.
|
13
|
+
h[:bestblockhash] = best_block.header.block_id
|
14
14
|
h[:chainwork] = best_block.header.work
|
15
15
|
h[:mediantime] = node.chain.mtp(best_block.hash)
|
16
16
|
h
|
@@ -22,21 +22,23 @@ module Bitcoin
|
|
22
22
|
end
|
23
23
|
|
24
24
|
# get block header information.
|
25
|
-
|
26
|
-
|
25
|
+
# @param [String] block_id block hash(big endian)
|
26
|
+
def getblockheader(block_id, verbose)
|
27
|
+
block_hash = block_id.rhex
|
28
|
+
entry = node.chain.find_entry_by_hash(block_hash)
|
27
29
|
if verbose
|
28
30
|
{
|
29
|
-
hash:
|
31
|
+
hash: block_id,
|
30
32
|
height: entry.height,
|
31
33
|
version: entry.header.version,
|
32
34
|
versionHex: entry.header.version.to_s(16),
|
33
|
-
merkleroot: entry.header.merkle_root,
|
35
|
+
merkleroot: entry.header.merkle_root.rhex,
|
34
36
|
time: entry.header.time,
|
35
|
-
mediantime: node.chain.mtp(
|
37
|
+
mediantime: node.chain.mtp(block_hash),
|
36
38
|
nonce: entry.header.nonce,
|
37
39
|
bits: entry.header.bits.to_s(16),
|
38
|
-
previousblockhash: entry.prev_hash,
|
39
|
-
nextblockhash: node.chain.next_hash(
|
40
|
+
previousblockhash: entry.prev_hash.rhex,
|
41
|
+
nextblockhash: node.chain.next_hash(block_hash).rhex
|
40
42
|
}
|
41
43
|
else
|
42
44
|
entry.header.to_payload.bth
|
@@ -94,8 +96,29 @@ module Bitcoin
|
|
94
96
|
|
95
97
|
# get current wallet information.
|
96
98
|
def getwalletinfo
|
99
|
+
node.wallet ? node.wallet.to_h : {}
|
100
|
+
end
|
101
|
+
|
102
|
+
# get the list of current Wallet accounts.
|
103
|
+
def listaccounts
|
97
104
|
return {} unless node.wallet
|
98
|
-
|
105
|
+
accounts = {}
|
106
|
+
node.wallet.accounts.each do |a|
|
107
|
+
accounts[a.name] = node.wallet.get_balance(a)
|
108
|
+
end
|
109
|
+
accounts
|
110
|
+
end
|
111
|
+
|
112
|
+
# encrypt wallet.
|
113
|
+
def encryptwallet(passphrase)
|
114
|
+
return nil unless node.wallet
|
115
|
+
node.wallet.encrypt(passphrase)
|
116
|
+
"The wallet 'wallet_id: #{node.wallet.wallet_id}' has been encrypted."
|
117
|
+
end
|
118
|
+
|
119
|
+
# create new bitcoin address for receiving payments.
|
120
|
+
def getnewaddress(account_name)
|
121
|
+
node.wallet.generate_new_address(account_name)
|
99
122
|
end
|
100
123
|
|
101
124
|
end
|
@@ -52,13 +52,15 @@ module Bitcoin
|
|
52
52
|
db.save_entry(entry)
|
53
53
|
entry
|
54
54
|
else
|
55
|
-
|
56
|
-
|
55
|
+
unless find_entry_by_hash(header.hash)
|
56
|
+
# TODO implements recovery process
|
57
|
+
raise "header's previous hash(#{header.prev_hash}) does not match current best block's(#{best_block.hash})."
|
58
|
+
end
|
57
59
|
end
|
58
60
|
end
|
59
61
|
|
60
62
|
# get next block hash for specified +hash+
|
61
|
-
# @param [String] hash the block hash
|
63
|
+
# @param [String] hash the block hash(little endian)
|
62
64
|
# @return [String] the next block hash. If it does not exist yet, return nil.
|
63
65
|
def next_hash(hash)
|
64
66
|
db.next_hash(hash)
|
data/lib/bitcoin/tx.rb
CHANGED
@@ -70,12 +70,20 @@ module Bitcoin
|
|
70
70
|
tx
|
71
71
|
end
|
72
72
|
|
73
|
+
def hash
|
74
|
+
Bitcoin.double_sha256(serialize_old_format).bth
|
75
|
+
end
|
76
|
+
|
73
77
|
def txid
|
74
|
-
|
78
|
+
hash.rhex
|
79
|
+
end
|
80
|
+
|
81
|
+
def witness_hash
|
82
|
+
Bitcoin.double_sha256(to_payload).bth
|
75
83
|
end
|
76
84
|
|
77
85
|
def wtxid
|
78
|
-
|
86
|
+
witness_hash.rhex
|
79
87
|
end
|
80
88
|
|
81
89
|
# get the witness commitment of coinbase tx.
|
data/lib/bitcoin/version.rb
CHANGED
@@ -4,30 +4,36 @@ module Bitcoin
|
|
4
4
|
# the account in BIP-44
|
5
5
|
class Account
|
6
6
|
|
7
|
-
PURPOSE_TYPE = {legacy: 44, nested_witness: 49}
|
7
|
+
PURPOSE_TYPE = {legacy: 44, nested_witness: 49, native_segwit: 84}
|
8
8
|
|
9
|
-
attr_reader :purpose # either 44 or 49
|
9
|
+
attr_reader :purpose # either 44 or 49 or 84
|
10
10
|
attr_reader :index # BIP-44 index
|
11
11
|
attr_reader :name # account name
|
12
|
+
attr_reader :account_key # account xpub key Bitcoin::ExtPubkey
|
12
13
|
attr_accessor :receive_depth # receive address depth(address index)
|
13
14
|
attr_accessor :change_depth # change address depth(address index)
|
14
15
|
attr_accessor :lookahead
|
15
16
|
attr_accessor :wallet
|
16
17
|
|
17
|
-
def initialize(purpose = PURPOSE_TYPE[:
|
18
|
+
def initialize(account_key, purpose = PURPOSE_TYPE[:native_segwit], index = 0, name = '')
|
19
|
+
validate_params!(account_key, purpose, index)
|
18
20
|
@purpose = purpose
|
19
21
|
@index = index
|
20
22
|
@name = name
|
21
23
|
@receive_depth = 0
|
22
24
|
@change_depth = 0
|
23
25
|
@lookahead = 10
|
26
|
+
@account_key = account_key
|
24
27
|
end
|
25
28
|
|
26
29
|
def self.parse_from_payload(payload)
|
30
|
+
buf = StringIO.new(payload)
|
31
|
+
account_key = Bitcoin::ExtPubkey.parse_from_payload(buf.read(78))
|
32
|
+
payload = buf.read
|
27
33
|
name, payload = Bitcoin.unpack_var_string(payload)
|
28
34
|
name = name.force_encoding('utf-8')
|
29
35
|
purpose, index, receive_depth, change_depth, lookahead = payload.unpack('I*')
|
30
|
-
a = Account.new(purpose, index, name)
|
36
|
+
a = Account.new(account_key, purpose, index, name)
|
31
37
|
a.receive_depth = receive_depth
|
32
38
|
a.change_depth = change_depth
|
33
39
|
a.lookahead = lookahead
|
@@ -35,31 +41,31 @@ module Bitcoin
|
|
35
41
|
end
|
36
42
|
|
37
43
|
def to_payload
|
38
|
-
payload =
|
44
|
+
payload = account_key.to_payload
|
45
|
+
payload << Bitcoin.pack_var_string(name.unpack('H*').first.htb)
|
39
46
|
payload << [purpose, index, receive_depth, change_depth, lookahead].pack('I*')
|
40
47
|
payload
|
41
48
|
end
|
42
49
|
|
43
50
|
# whether support witness
|
44
51
|
def witness?
|
45
|
-
|
52
|
+
[PURPOSE_TYPE[:nested_witness], PURPOSE_TYPE[:native_segwit]].include?(purpose)
|
46
53
|
end
|
47
54
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
@
|
55
|
+
# create new receive key
|
56
|
+
# @return [Bitcoin::ExtKey]
|
57
|
+
def create_receive
|
58
|
+
@receive_depth += 1
|
52
59
|
save
|
60
|
+
derive_key(0, @receive_depth)
|
53
61
|
end
|
54
62
|
|
55
|
-
#
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
def derive_change(address_index)
|
62
|
-
derive_key(1, address_index)
|
63
|
+
# create new change key
|
64
|
+
# # @return [Bitcoin::ExtKey]
|
65
|
+
def create_change
|
66
|
+
@change_depth += 1
|
67
|
+
save
|
68
|
+
derive_key(1, @change_depth)
|
63
69
|
end
|
64
70
|
|
65
71
|
# save this account payload to database.
|
@@ -68,13 +74,53 @@ module Bitcoin
|
|
68
74
|
end
|
69
75
|
|
70
76
|
# get the list of derived keys for receive key.
|
77
|
+
# @return [Array[Bitcoin::ExtPubkey]]
|
71
78
|
def derived_receive_keys
|
72
|
-
receive_depth.times.map{|i|derive_key(0,i)}
|
79
|
+
(receive_depth + 1).times.map{|i|derive_key(0,i)}
|
73
80
|
end
|
74
81
|
|
75
82
|
# get the list of derived keys for change key.
|
83
|
+
# @return [Array[Bitcoin::ExtPubkey]]
|
76
84
|
def derived_change_keys
|
77
|
-
change_depth.times.map{|i|derive_key(1,i)}
|
85
|
+
(change_depth + 1).times.map{|i|derive_key(1,i)}
|
86
|
+
end
|
87
|
+
|
88
|
+
# get account type label.
|
89
|
+
def type
|
90
|
+
case purpose
|
91
|
+
when PURPOSE_TYPE[:legacy]
|
92
|
+
'pubkeyhash'
|
93
|
+
when PURPOSE_TYPE[:nested_witness]
|
94
|
+
'p2wpkh-p2sh'
|
95
|
+
when PURPOSE_TYPE[:native_segwit]
|
96
|
+
'p2wpkh'
|
97
|
+
else
|
98
|
+
'unknown'
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# account derivation path
|
103
|
+
def path
|
104
|
+
"m/#{purpose}'/#{Bitcoin.chain_params.bip44_coin_type}'/#{index}'"
|
105
|
+
end
|
106
|
+
|
107
|
+
def watch_only
|
108
|
+
false # TODO implements import watch only address.
|
109
|
+
end
|
110
|
+
|
111
|
+
# get data elements tobe monitored with Bloom Filter.
|
112
|
+
# @return [Array[String]]
|
113
|
+
def watch_targets
|
114
|
+
derived_receive_keys.map(&:hash160) + derived_change_keys.map(&:hash160)
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_h
|
118
|
+
{
|
119
|
+
name: name, type: type, index: index, receive_depth: receive_depth, change_depth: change_depth,
|
120
|
+
look_ahead: lookahead, receive_address: derive_key(0, receive_depth).addr,
|
121
|
+
change_address: derive_key(1, change_depth).addr,
|
122
|
+
account_key: account_key.to_base58, path: path, watch_only: watch_only
|
123
|
+
}
|
78
124
|
end
|
79
125
|
|
80
126
|
private
|
@@ -83,11 +129,11 @@ module Bitcoin
|
|
83
129
|
account_key.derive(branch).derive(address_index)
|
84
130
|
end
|
85
131
|
|
86
|
-
def account_key
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
132
|
+
def validate_params!(account_key, purpose, index)
|
133
|
+
raise 'account_key must be an instance of Bitcoin::ExtPubkey.' unless account_key.is_a?(Bitcoin::ExtPubkey)
|
134
|
+
raise 'Account key and index does not match.' unless account_key.number == (index + 2**31)
|
135
|
+
version_bytes = Bitcoin::ExtPubkey.version_from_purpose(purpose + 2**31)
|
136
|
+
raise 'The purpose and the account key do not match.' unless account_key.version == version_bytes
|
91
137
|
end
|
92
138
|
|
93
139
|
end
|
data/lib/bitcoin/wallet/base.rb
CHANGED
@@ -49,21 +49,48 @@ module Bitcoin
|
|
49
49
|
end
|
50
50
|
|
51
51
|
# get account list based on BIP-44
|
52
|
-
def accounts
|
53
|
-
|
52
|
+
def accounts(purpose = nil)
|
53
|
+
list = []
|
54
|
+
db.accounts.each do |raw|
|
54
55
|
a = Account.parse_from_payload(raw)
|
56
|
+
next if purpose && purpose != a.purpose
|
55
57
|
a.wallet = self
|
56
|
-
a
|
58
|
+
list << a
|
57
59
|
end
|
60
|
+
list
|
58
61
|
end
|
59
62
|
|
60
|
-
|
61
|
-
|
63
|
+
# create new account
|
64
|
+
# @param [Integer] purpose BIP44's purpose.
|
65
|
+
# @param [String] name a account name.
|
66
|
+
# @return [Bitcoin::Wallet::Account]
|
67
|
+
def create_account(purpose = Account::PURPOSE_TYPE[:native_segwit], name)
|
68
|
+
raise ArgumentError.new('Account already exists.') if find_account(name, purpose)
|
69
|
+
index = accounts.size
|
70
|
+
path = "m/#{purpose}'/#{Bitcoin.chain_params.bip44_coin_type}'/#{index}'"
|
71
|
+
account_key = master_key.derive(path).ext_pubkey
|
72
|
+
account = Account.new(account_key, purpose, index, name)
|
62
73
|
account.wallet = self
|
63
|
-
account.
|
74
|
+
account.save
|
64
75
|
account
|
65
76
|
end
|
66
77
|
|
78
|
+
# get wallet balance.
|
79
|
+
# @param [Bitcoin::Wallet::Account] account a account in the wallet.
|
80
|
+
def get_balance(account)
|
81
|
+
# TODO get from utxo db.
|
82
|
+
0.00000000
|
83
|
+
end
|
84
|
+
|
85
|
+
# create new bitcoin address for receiving payments.
|
86
|
+
# @param [String] account_name an account name.
|
87
|
+
# @return [String] generated address.
|
88
|
+
def generate_new_address(account_name)
|
89
|
+
account = find_account(account_name)
|
90
|
+
raise ArgumentError.new('Account does not exist.') unless account
|
91
|
+
account.create_receive.addr
|
92
|
+
end
|
93
|
+
|
67
94
|
# get wallet version.
|
68
95
|
def version
|
69
96
|
db.version
|
@@ -83,7 +110,8 @@ module Bitcoin
|
|
83
110
|
# encrypt wallet
|
84
111
|
# @param [String] passphrase the wallet passphrase
|
85
112
|
def encrypt(passphrase)
|
86
|
-
|
113
|
+
master_key.encrypt(passphrase)
|
114
|
+
db.register_master_key(master_key)
|
87
115
|
end
|
88
116
|
|
89
117
|
# decrypt wallet
|
@@ -92,6 +120,18 @@ module Bitcoin
|
|
92
120
|
|
93
121
|
end
|
94
122
|
|
123
|
+
# wallet information
|
124
|
+
def to_h
|
125
|
+
a = accounts.map(&:to_h)
|
126
|
+
{ wallet_id: wallet_id, version: version, account_depth: a.size, accounts: a, master: {encrypted: master_key.encrypted} }
|
127
|
+
end
|
128
|
+
|
129
|
+
# get data elements tobe monitored with Bloom Filter.
|
130
|
+
# @return [Array[String]]
|
131
|
+
def watch_targets
|
132
|
+
accounts.map(&:watch_targets).flatten
|
133
|
+
end
|
134
|
+
|
95
135
|
private
|
96
136
|
|
97
137
|
def initialize(wallet_id, path_prefix)
|
@@ -105,6 +145,11 @@ module Bitcoin
|
|
105
145
|
Dir.exist?(path)
|
106
146
|
end
|
107
147
|
|
148
|
+
# find account using +account_name+
|
149
|
+
def find_account(account_name, purpose = nil)
|
150
|
+
accounts(purpose).find{|a| a.name == account_name}
|
151
|
+
end
|
152
|
+
|
108
153
|
end
|
109
154
|
|
110
155
|
end
|
data/lib/bitcoin/wallet/db.rb
CHANGED
@@ -31,7 +31,8 @@ module Bitcoin
|
|
31
31
|
|
32
32
|
def save_account(account)
|
33
33
|
level_db.batch do
|
34
|
-
|
34
|
+
id = [account.purpose, account.index].pack('I*').bth
|
35
|
+
key = KEY_PREFIX[:account] + id
|
35
36
|
level_db.put(key, account.to_payload)
|
36
37
|
end
|
37
38
|
end
|
@@ -61,9 +61,25 @@ module Bitcoin
|
|
61
61
|
Bitcoin::ExtKey.generate_master(seed)
|
62
62
|
end
|
63
63
|
|
64
|
+
# derive child key using derivation path.
|
65
|
+
# @return [Bitcoin::ExtKey]
|
66
|
+
def derive(path)
|
67
|
+
derived_key = key
|
68
|
+
path.split('/').each_with_index do|p, index|
|
69
|
+
if index == 0
|
70
|
+
raise ArgumentError.new("#{path} is invalid format.") unless p == 'm'
|
71
|
+
next
|
72
|
+
end
|
73
|
+
raise ArgumentError.new("#{path} is invalid format.") unless p.delete("'") =~ /^[0-9]+$/
|
74
|
+
num = (p[-1] == "'" ? p.delete("'").to_i + 2**31 : p.to_i)
|
75
|
+
derived_key = derived_key.derive(num)
|
76
|
+
end
|
77
|
+
derived_key
|
78
|
+
end
|
79
|
+
|
64
80
|
# encrypt seed
|
65
81
|
def encrypt(passphrase)
|
66
|
-
raise '
|
82
|
+
raise 'The wallet is already encrypted.' if encrypted
|
67
83
|
@salt = SecureRandom.hex(16)
|
68
84
|
enc = OpenSSL::Cipher.new('AES-256-CBC')
|
69
85
|
enc.encrypt
|
@@ -77,7 +93,7 @@ module Bitcoin
|
|
77
93
|
|
78
94
|
# decrypt seed
|
79
95
|
def decrypt(passphrase)
|
80
|
-
raise '
|
96
|
+
raise 'The wallet is not encrypted.' unless encrypted
|
81
97
|
dec = OpenSSL::Cipher.new('AES-256-CBC')
|
82
98
|
dec.decrypt
|
83
99
|
dec.key, dec.iv = key_iv(dec, passphrase)
|
data/lib/bitcoin.rb
CHANGED
@@ -32,6 +32,7 @@ module Bitcoin
|
|
32
32
|
autoload :MerkleTree, 'bitcoin/merkle_tree'
|
33
33
|
autoload :Key, 'bitcoin/key'
|
34
34
|
autoload :ExtKey, 'bitcoin/ext_key'
|
35
|
+
autoload :ExtPubkey, 'bitcoin/ext_key'
|
35
36
|
autoload :Opcodes, 'bitcoin/opcodes'
|
36
37
|
autoload :Node, 'bitcoin/node'
|
37
38
|
autoload :Base58, 'bitcoin/base58'
|
@@ -101,6 +102,11 @@ module Bitcoin
|
|
101
102
|
[self].pack('H*')
|
102
103
|
end
|
103
104
|
|
105
|
+
# reverse hex string endian
|
106
|
+
def rhex
|
107
|
+
htb.reverse.bth
|
108
|
+
end
|
109
|
+
|
104
110
|
# get opcode
|
105
111
|
def opcode
|
106
112
|
case encoding
|
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.
|
4
|
+
version: 0.1.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- azuchi
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-03-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: ecdsa
|