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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 99f5168564a4d436b4a3a43ce30fc4cf0657f4feb0f96cfa57362de3a322846b
4
- data.tar.gz: 0bf7749a29bfcebb5006a5a01f47dabeb766d98269476dfa75f2d524af6d6da2
3
+ metadata.gz: 3d66a90af2b70a6494a6b38d0602a4442a0173e27294cec5b389149b77bc7360
4
+ data.tar.gz: 1b830903242397fece09064f51a150b9716ef1d06409f597ca60c6ee76e80ebc
5
5
  SHA512:
6
- metadata.gz: c23353ea258a891d79fff2f91a0e8661f389dc6045ca0199955dfe3fb91cdb01be5dc857553c7073633c7f34f0603991f6761403f787aea9bf306586124ccea4
7
- data.tar.gz: '0597bdd738e1ffc9b8189ec1aaf05245b2cc988bd4ce3a92ad35bf8182cfb3864b59209c7a4894cf0c6d10d5fabaf3a9f2e0da10c7afa25f595969274ca3f5e1'
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(&:txid)).merkle_root
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
- wtxid_list = [COINBASE_WTXID]
47
- wtxid_list.concat(transactions[1..-1].map{|tx| tx.wtxid})
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(wtxid_list).merkle_root
50
- Digest::SHA256.digest(Digest::SHA256.digest(
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.
@@ -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.reverse.bth, merkle_root.reverse.bth, time, bits, nonce)
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.reverse, merkle_root.htb.reverse, time, bits, nonce].pack('Va32a32VVV')
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
- hash.hex < difficulty_target
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).reverse.bth
80
+ Bitcoin.double_sha256(to_payload).bth
76
81
  end
77
82
 
78
83
  end
@@ -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 initialize(elements_length, fp_rate, tweak=0)
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
- @filter = Array.new(len, 0)
25
+ filter = Array.new(len, 0)
19
26
  # The number of hash functions required is given by S * 8 / N * log(2)
20
- @hash_funcs = [[(@filter.size * 8 * LN2 / elements_length).to_i, MAX_HASH_FUNCS].min, 1].max
21
- @tweak = tweak
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
- @hash_funcs.times do |i|
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
- @hash_funcs.times do |i|
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
- @filter.fill(0)
53
+ filter.fill(0)
47
54
  @full = false
48
55
  end
49
56
 
50
57
  def to_a
51
- @filter
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 + @tweak) & 0xffffffff) % (@filter.length * 8)
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
- @filter[data >> 3] |= (1 << (7 & data))
67
+ filter[data >> 3] |= (1 << (7 & data))
61
68
  end
62
69
 
63
70
  def check_bit(data)
64
- @filter[data >> 3] & (1 << (7 & data)) != 0
71
+ filter[data >> 3] & (1 << (7 & data)) != 0
65
72
  end
66
73
 
67
74
  def full?
68
- @full |= @filter.all? {|byte| byte == 0xff}
75
+ @full |= filter.all? {|byte| byte == 0xff}
69
76
  end
70
77
  end
71
78
  end
@@ -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
@@ -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
- Bitcoin.chain_params.extended_privkey_version.htb << [depth].pack('C') <<
43
- parent_fingerprint.htb << [number].pack('N') << chain_code << [0x00].pack('C') << key.priv_key.htb
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
- key.to_p2pkh
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
- # import private key from Base58 private key address
114
- def self.from_base58(address)
115
- data = StringIO.new(Base58.decode(address).htb)
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
- data.read(4).bth # version
118
- ext_key.depth = data.read(1).unpack('C').first
119
- ext_key.parent_fingerprint = data.read(4).bth
120
- ext_key.number = data.read(4).unpack('N').first
121
- ext_key.chain_code = data.read(32)
122
- data.read(1) # 0x00
123
- ext_key.key = Bitcoin::Key.new(priv_key: data.read(32).bth)
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
- Bitcoin.chain_params.extended_pubkey_version.htb << [depth].pack('C') <<
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
- Bitcoin::Key.new(pubkey: pubkey).to_p2pkh
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 segwit p2wpkh address
157
- def segwit_addr
158
- hash160 = Bitcoin.hash160(pub)
159
- p2wpkh = [ ["00", "14", hash160].join ].pack("H*").bth
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
- # import pub key from Base58 private key address
207
- def self.from_base58(address)
208
- data = StringIO.new(Base58.decode(address).htb)
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
- data.read(4).bth # version
211
- ext_pubkey.depth = data.read(1).unpack('C').first
212
- ext_pubkey.parent_fingerprint = data.read(4).bth
213
- ext_pubkey.number = data.read(4).unpack('N').first
214
- ext_pubkey.chain_code = data.read(32)
215
- ext_pubkey.pubkey = data.read(33).bth
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(Bitcoin.hash160(pubkey)).to_addr
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(Bitcoin.hash160(pubkey)).to_addr
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?
@@ -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
- Digest::SHA256.digest(Digest::SHA256.digest(
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 # bin format
15
- attr_accessor :func_count
16
- attr_accessor :tweak
14
+ attr_accessor :filter
17
15
  attr_accessor :flag
18
16
 
19
- def initialize(filter, func_count, tweak = 0, flag = BLOOM_UPDATE_ALL)
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*') << [func_count, tweak, flag].pack('VVC')
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
@@ -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
- bloom.to_a,
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
 
@@ -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(bloom)
79
- peers.each { |peer| peer.send_filter_load(bloom) }
81
+ def filter_load(peer)
82
+ peer.send_filter_load(node.bloom)
80
83
  end
81
84
 
82
85
  # add element to bloom filter.
@@ -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
@@ -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
- # TODO : load public keys in wallet.
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
- @bloom.add(element)
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.hash
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
- def getblockheader(hash, verbose)
26
- entry = node.chain.find_entry_by_hash(hash)
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: 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(hash),
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(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
- {wallet_id: node.wallet.wallet_id, version: node.wallet.version}
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
@@ -75,7 +75,7 @@ module Bitcoin
75
75
  # generate height key
76
76
  def height_key(height)
77
77
  height = height.to_even_length_hex
78
- KEY_PREFIX[:height] + height.htb.reverse.bth
78
+ KEY_PREFIX[:height] + height.rhex
79
79
  end
80
80
 
81
81
  def connect_entry(entry)
@@ -52,13 +52,15 @@ module Bitcoin
52
52
  db.save_entry(entry)
53
53
  entry
54
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})."
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
- Bitcoin.double_sha256(serialize_old_format).reverse.bth
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
- Bitcoin.double_sha256(to_payload).reverse.bth
86
+ witness_hash.rhex
79
87
  end
80
88
 
81
89
  # get the witness commitment of coinbase tx.
@@ -1,3 +1,3 @@
1
1
  module Bitcoin
2
- VERSION = "0.1.6"
2
+ VERSION = "0.1.7"
3
3
  end
@@ -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[:nested_witness], index = 0, name = '')
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 = Bitcoin.pack_var_string(name.unpack('H*').first.htb)
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
- purpose == PURPOSE_TYPE[:nested_witness]
52
+ [PURPOSE_TYPE[:nested_witness], PURPOSE_TYPE[:native_segwit]].include?(purpose)
46
53
  end
47
54
 
48
- def init
49
- @receive_depth = lookahead
50
- @change_depth = lookahead
51
- @index = wallet.accounts.size
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
- # derive receive key
56
- def derive_receive(address_index)
57
- derive_key(0, address_index)
58
- end
59
-
60
- # derive change key
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
- return @cached_account_key if @cached_account_key
88
- coin_type = Bitcoin.chain_params.bip44_coin_type
89
- # m / purpose' / coin_type' / account_index'
90
- @cached_account_key = wallet.master_key.key.derive(2**31 + purpose).derive(2**31 + coin_type).derive(2**31 + index)
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
@@ -49,21 +49,48 @@ module Bitcoin
49
49
  end
50
50
 
51
51
  # get account list based on BIP-44
52
- def accounts
53
- db.accounts.map do |raw|
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
- def create_account(purpose = Account::PURPOSE_TYPE[:nested_witness], index = 0, name)
61
- account = Account.new(purpose, index, name)
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.init
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
@@ -31,7 +31,8 @@ module Bitcoin
31
31
 
32
32
  def save_account(account)
33
33
  level_db.batch do
34
- key = KEY_PREFIX[:account] + account.index.to_s(16).rjust(8, '0')
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 'seed already encrypted.' if encrypted
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 'seed is not encrypted.' unless encrypted
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.6
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-02-18 00:00:00.000000000 Z
11
+ date: 2018-03-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ecdsa