bitcoin-ruby 0.0.10 → 0.0.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +1 -0
  3. data/COPYING +1 -1
  4. data/Gemfile.lock +9 -10
  5. data/README.rdoc +1 -1
  6. data/Rakefile +4 -2
  7. data/lib/bitcoin.rb +4 -2
  8. data/lib/bitcoin/bloom_filter.rb +125 -0
  9. data/lib/bitcoin/builder.rb +34 -9
  10. data/lib/bitcoin/ext_key.rb +191 -0
  11. data/lib/bitcoin/ffi/openssl.rb +1 -1
  12. data/lib/bitcoin/key.rb +6 -4
  13. data/lib/bitcoin/protocol.rb +13 -11
  14. data/lib/bitcoin/protocol/block.rb +38 -2
  15. data/lib/bitcoin/protocol/parser.rb +8 -0
  16. data/lib/bitcoin/protocol/partial_merkle_tree.rb +61 -0
  17. data/lib/bitcoin/protocol/script_witness.rb +31 -0
  18. data/lib/bitcoin/protocol/tx.rb +170 -10
  19. data/lib/bitcoin/protocol/txin.rb +8 -0
  20. data/lib/bitcoin/protocol/version.rb +2 -1
  21. data/lib/bitcoin/script.rb +58 -8
  22. data/lib/bitcoin/version.rb +1 -1
  23. data/spec/bitcoin/bloom_filter_spec.rb +23 -0
  24. data/spec/bitcoin/builder_spec.rb +12 -0
  25. data/spec/bitcoin/ext_key_spec.rb +180 -0
  26. data/spec/bitcoin/fixtures/filteredblock-0.bin +0 -0
  27. data/spec/bitcoin/fixtures/rawblock-testnet-1151351.bin +0 -0
  28. data/spec/bitcoin/fixtures/rawtx-p2wpkh.bin +0 -0
  29. data/spec/bitcoin/fixtures/rawtx-p2wpkh.json +67 -0
  30. data/spec/bitcoin/fixtures/tx-0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3.json +139 -0
  31. data/spec/bitcoin/fixtures/tx-28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f.json +34 -0
  32. data/spec/bitcoin/protocol/bip143_spec.rb +116 -0
  33. data/spec/bitcoin/protocol/block_spec.rb +27 -0
  34. data/spec/bitcoin/protocol/partial_merkle_tree_spec.rb +38 -0
  35. data/spec/bitcoin/protocol/tx_spec.rb +134 -1
  36. data/spec/bitcoin/script/script_spec.rb +53 -2
  37. metadata +27 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: fa1430d4dfa8bcf7f4e23a098341ef1803797d8b
4
- data.tar.gz: 1d4f1c0a5b163c7d42d07e4056d6a497719167b4
3
+ metadata.gz: 1103ce4c2cb0a379a5de5edfb2a9cfb0cfcb19e0
4
+ data.tar.gz: db4312fc6546cd7c357ab95fc2293814025f7f5c
5
5
  SHA512:
6
- metadata.gz: a790a81f8a5bd8a94e8794e599b0ab5ca62f2b4568efce9924ffbc1fabd2805307ef9e8695cb2c9383907eff3ab62081fc8dd798d1985bcd11623e0155070c84
7
- data.tar.gz: 5ab37c03ed8b4ba6f15a95c1ab29d564c2b5200309b75ef5667f9dc9f3003e267f8bf7c14ddf42fe9b56f53c6e26e221850e0f74af81e1fa731cfdd88e87218f
6
+ metadata.gz: 2562f4d8edaa26237e99d775dc263127c98d31d9570359811a453c1076d017f0f41cc07fd64bb63f263d31e4d3ab0295ca53cb55ce31d75a6ba5ad25a0c54371
7
+ data.tar.gz: c5d9ceb1c9c253ce1b761343aa5fd665fb3eba9ffa423add7bea8d4f6bd0abfd6b23d3969c282a54fee21f31bb96897c74e5cf700c0823786847315617273dd9
data/.travis.yml CHANGED
@@ -2,3 +2,4 @@ language: ruby
2
2
  rvm:
3
3
  - 2.2.0
4
4
  - 2.3.2
5
+ - 2.4.1
data/COPYING CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2015 Julian Langschaedel <meta.rb@gmail.com>
1
+ Copyright (c) 2017 Julian Langschaedel <meta.rb@gmail.com>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/Gemfile.lock CHANGED
@@ -1,22 +1,21 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- bitcoin-ruby (0.0.8)
4
+ bitcoin-ruby (0.0.11)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
8
8
  specs:
9
9
  bacon (1.2.0)
10
- eventmachine (1.0.8)
11
- ffi (1.9.10)
12
- ffi-compiler (0.1.3)
10
+ eventmachine (1.2.3)
11
+ ffi (1.9.18)
12
+ ffi-compiler (1.0.1)
13
13
  ffi (>= 1.0.0)
14
14
  rake
15
- minitest (5.8.2)
16
- rake (10.4.2)
17
- scrypt (2.0.2)
18
- ffi-compiler (>= 0.0.2)
19
- rake
15
+ minitest (5.10.2)
16
+ rake (12.0.0)
17
+ scrypt (3.0.5)
18
+ ffi-compiler (>= 1.0, < 2.0)
20
19
 
21
20
  PLATFORMS
22
21
  ruby
@@ -31,4 +30,4 @@ DEPENDENCIES
31
30
  scrypt
32
31
 
33
32
  BUNDLED WITH
34
- 1.10.6
33
+ 1.14.6
data/README.rdoc CHANGED
@@ -36,7 +36,7 @@ We assume you already have a ruby 1.9 or 2.0 compatible interpreter and rubygems
36
36
 
37
37
  if you want to have it available system-wide, just build the gem and install it:
38
38
 
39
- gem build bitcoin-ruby.gemspec && gem install bitcoin-ruby-0.0.5.gem
39
+ gem build bitcoin-ruby.gemspec && gem install bitcoin-ruby-0.0.11.gem
40
40
 
41
41
  Note that some aspects of the library (such as networking, storage, etc.) need
42
42
  additional dependencies which are not specified in the gemspec. The core requirements are
data/Rakefile CHANGED
@@ -23,8 +23,10 @@ task :bacon do
23
23
  require 'matrix'
24
24
 
25
25
  specs = PROJECT_SPECS
26
- #specs.delete_if{|i| File.basename(i) == 'storage_spec.rb' } # skip for now
27
- specs.delete_if{|i| File.basename(i) == 'secp256k1_spec.rb' } # skip for now
26
+ unless ENV["SECP256K1_LIB_PATH"]
27
+ # skip when missing secp256k1 shared lib
28
+ specs.delete_if{|i| ['secp256k1_spec.rb', 'bip143_spec.rb'].include?(File.basename(i))}
29
+ end
28
30
 
29
31
  # E.g. SPEC=specs/bitcoin/script/ to run script-related specs only.
30
32
  if spec_mask = ENV["SPEC"]
data/lib/bitcoin.rb CHANGED
@@ -15,7 +15,9 @@ module Bitcoin
15
15
  autoload :VERSION, 'bitcoin/version'
16
16
  autoload :Logger, 'bitcoin/logger'
17
17
  autoload :Key, 'bitcoin/key'
18
+ autoload :ExtKey, 'bitcoin/ext_key'
18
19
  autoload :Builder, 'bitcoin/builder'
20
+ autoload :BloomFilter,'bitcoin/bloom_filter'
19
21
 
20
22
  autoload :Dogecoin, 'bitcoin/dogecoin'
21
23
  autoload :Litecoin, 'bitcoin/litecoin'
@@ -270,7 +272,7 @@ module Bitcoin
270
272
  branch.each do |hash|
271
273
  a, b = *( idx & 1 == 0 ? [target, hash] : [hash, target] )
272
274
  idx >>= 1;
273
- target = bitcoin_mrkl( a, b )
275
+ target = bitcoin_mrkl( a, b )
274
276
  end
275
277
  target
276
278
  end
@@ -814,7 +816,7 @@ module Bitcoin
814
816
  proof_of_work_limit: 0x1e0fffff,
815
817
  alert_pubkeys: [],
816
818
  known_nodes: [
817
- "localhost",
819
+ "localhost",
818
820
  "testnets.chain.so",
819
821
  ],
820
822
  checkpoints: {
@@ -0,0 +1,125 @@
1
+ module Bitcoin
2
+ class BloomFilter
3
+ SEED_SHIFT = 0xfba4c795
4
+ BIT_MASK = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80]
5
+
6
+ MAX_FILTER_SIZE = 36000
7
+ MAX_HASH_FUNCS = 50
8
+
9
+ # flags for filterload message
10
+ BLOOM_UPDATE_NONE = 0
11
+ BLOOM_UPDATE_ALL = 1
12
+ BLOOM_UPDATE_P2PUBKEY_ONLY = 2
13
+
14
+ attr_reader :filter, :nfunc, :tweak
15
+
16
+ def initialize(elements, fp_rate, tweak)
17
+ init_filter(elements, fp_rate)
18
+ @tweak = tweak
19
+ end
20
+
21
+ def add_data(data)
22
+ @nfunc.times.each do |fi|
23
+ idx = calc_index(data, fi)
24
+ i = idx / 8
25
+ @filter[i] = (@filter[i].ord | BIT_MASK[idx % 8]).chr
26
+ end
27
+ end
28
+
29
+ def contains?(data)
30
+ @nfunc.times.all? do |fi|
31
+ idx = calc_index(data, fi)
32
+ i = idx / 8
33
+ @filter[i].ord & BIT_MASK[idx % 8] != 0
34
+ end
35
+ end
36
+
37
+ def add_address(address)
38
+ add_data(Bitcoin.hash160_from_address(address).htb)
39
+ end
40
+
41
+ def add_outpoint(prev_tx_hash, prev_output)
42
+ add_data(prev_tx_hash.htb_reverse + [prev_output].pack('V'))
43
+ end
44
+
45
+ private
46
+
47
+ #
48
+ # calculate filter size and number of funcs.
49
+ # See: https://github.com/bitcoin/bips/blob/master/bip-0037.mediawiki
50
+ #
51
+ def init_filter(elements, fp_rate)
52
+ ln2 = Math.log(2)
53
+
54
+ # using #ceil instead of #floor may be better, but it's bitcoinj's way
55
+
56
+ calc_m = (-Math.log(fp_rate) * elements / ln2 / ln2 / 8).floor;
57
+ @filter_size = [1, [calc_m, MAX_FILTER_SIZE].min].max;
58
+ @filter = "\x00" * @filter_size
59
+
60
+ calc_k = (@filter_size * 8 * ln2 / elements).floor
61
+ @nfunc = [1, [calc_k, MAX_HASH_FUNCS].min].max
62
+ end
63
+
64
+ def rotate_left32(x, r)
65
+ return (x << r) | (x >> (32 - r))
66
+ end
67
+
68
+ #
69
+ # calculate MurmurHash3
70
+ # See: https://github.com/aappleby/smhasher/blob/master/src/MurmurHash3.cpp
71
+ #
72
+ def calc_index(data, hash_index)
73
+ object = data.bytes
74
+ h1 = (hash_index * SEED_SHIFT + @tweak) & 0xffffffff
75
+ c1 = 0xcc9e2d51
76
+ c2 = 0x1b873593
77
+
78
+ num_blocks = (object.length / 4) * 4
79
+ i = 0
80
+ # body
81
+ while i < num_blocks
82
+ k1 = (object[i] & 0xFF) |
83
+ ((object[i + 1] & 0xFF) << 8) |
84
+ ((object[i + 2] & 0xFF) << 16) |
85
+ ((object[i + 3] & 0xFF) << 24)
86
+
87
+ k1 *= c1; k1 &= 0xffffffff
88
+ k1 = rotate_left32(k1, 15)
89
+ k1 *= c2; k1 &= 0xffffffff
90
+
91
+ h1 ^= k1
92
+ h1 = rotate_left32(h1, 13)
93
+ h1 = (h1 * 5 + 0xe6546b64) & 0xffffffff
94
+
95
+ i += 4
96
+ end
97
+
98
+ k1 = 0
99
+ flg = object.length & 3
100
+ if flg >= 3
101
+ k1 ^= (object[num_blocks + 2] & 0xff) << 16
102
+ end
103
+ if flg >= 2
104
+ k1 ^= (object[num_blocks + 1] & 0xff) << 8
105
+ end
106
+ if flg >= 1
107
+ k1 ^= (object[num_blocks] & 0xff)
108
+ k1 *= c1; k1 &= 0xffffffff
109
+ k1 = rotate_left32(k1, 15)
110
+ k1 *= c2; k1 &= 0xffffffff
111
+ h1 ^= k1
112
+ end
113
+
114
+ # finalization
115
+ h1 ^= object.length
116
+ h1 ^= h1 >> 16
117
+ h1 *= 0x85ebca6b; h1 &= 0xffffffff
118
+ h1 ^= h1 >> 13
119
+ h1 *= 0xc2b2ae35; h1 &= 0xffffffff
120
+ h1 ^= h1 >> 16
121
+
122
+ return (h1 & 0xffffffff) % (@filter_size * 8)
123
+ end
124
+ end
125
+ end
@@ -202,7 +202,7 @@ module Bitcoin
202
202
  end
203
203
 
204
204
  # run our tx through an encode/decode cycle to make sure that the binary format is sane
205
- raise "Payload Error" unless P::Tx.new(@tx.to_payload).to_payload == @tx.to_payload
205
+ raise "Payload Error" unless P::Tx.new(@tx.to_witness_payload).to_payload == @tx.to_payload
206
206
  @tx.instance_eval do
207
207
  @payload = to_payload
208
208
  @hash = hash_from_payload(@payload)
@@ -221,11 +221,11 @@ module Bitcoin
221
221
  def sig_hash_and_all_keys_exist?(inc, sig_script)
222
222
  return false unless @sig_hash && inc.has_keys?
223
223
  script = Bitcoin::Script.new(sig_script)
224
- return true if script.is_hash160? || script.is_pubkey? || (Bitcoin.namecoin? && script.is_namecoin?)
224
+ return true if script.is_hash160? || script.is_pubkey? || script.is_witness_v0_keyhash? || (Bitcoin.namecoin? && script.is_namecoin?)
225
225
  if script.is_multisig?
226
226
  return inc.has_multiple_keys? && inc.key.size >= script.get_signatures_required
227
227
  end
228
- raise "Script type must be hash160, pubkey or multisig"
228
+ raise "Script type must be hash160, pubkey, p2wpkh or multisig"
229
229
  end
230
230
 
231
231
  def add_empty_script_sig_to_input(i)
@@ -269,13 +269,24 @@ module Bitcoin
269
269
  sig_script ||= @prev_script
270
270
 
271
271
  # when a sig_script was found, generate the sig_hash to be signed
272
- @sig_hash = @tx.signature_hash_for_input(i, sig_script) if sig_script
272
+ if sig_script
273
+ script = Script.new(sig_script)
274
+ if script.is_witness_v0_keyhash?
275
+ @sig_hash = @tx.signature_hash_for_witness_input(i, sig_script, inc.value)
276
+ else
277
+ @sig_hash = @tx.signature_hash_for_input(i, sig_script)
278
+ end
279
+ end
273
280
 
274
281
  # when there is a sig_hash and one or more signature_keys were specified
275
282
  if sig_hash_and_all_keys_exist?(inc, sig_script)
276
283
  # add the script_sig to the txin
277
- @tx.in[i].script_sig = get_script_sig(inc)
278
-
284
+ if script.is_witness_v0_keyhash? # for p2wpkh
285
+ @tx.in[i].script_witness.stack << inc.sign(@sig_hash) + [Script::SIGHASH_TYPE[:all]].pack("C")
286
+ @tx.in[i].script_witness.stack << inc.key.pub.htb
287
+ else
288
+ @tx.in[i].script_sig = get_script_sig(inc)
289
+ end
279
290
  # double-check that the script_sig is valid to spend the given prev_script
280
291
  raise "Signature error" if @prev_script && !@tx.verify_input_signature(i, @prev_script)
281
292
  elsif inc.has_multiple_keys?
@@ -318,7 +329,7 @@ module Bitcoin
318
329
  #
319
330
  # If you want to spend a multisig output, just provide an array of keys to #signature_key.
320
331
  class TxInBuilder
321
- attr_reader :prev_tx, :prev_script, :redeem_script, :key, :coinbase_data
332
+ attr_reader :prev_tx, :prev_script, :redeem_script, :key, :coinbase_data, :prev_out_value
322
333
 
323
334
  def initialize
324
335
  @txin = P::TxIn.new
@@ -330,7 +341,7 @@ module Bitcoin
330
341
  # You can either pass the transaction, or just the tx hash.
331
342
  # If you pass only the hash, you need to pass the previous outputs
332
343
  # +script+ separately if you want the txin to be signed.
333
- def prev_out tx, idx = nil, script = nil
344
+ def prev_out tx, idx = nil, script = nil, prev_value = nil
334
345
  if tx.is_a?(Bitcoin::P::Tx)
335
346
  @prev_tx = tx
336
347
  @prev_out_hash = tx.binary_hash
@@ -340,6 +351,7 @@ module Bitcoin
340
351
  end
341
352
  @prev_out_script = script if script
342
353
  @prev_out_index = idx if idx
354
+ @prev_out_value = prev_value if prev_value
343
355
  end
344
356
 
345
357
  # Index of the output in the #prev_out transaction.
@@ -353,6 +365,15 @@ module Bitcoin
353
365
  @prev_out_script = script
354
366
  end
355
367
 
368
+ # Previous output's +value+. Needed when only spend segwit utxo.
369
+ def prev_out_value value
370
+ @prev_out_value = value
371
+ end
372
+
373
+ def value
374
+ @prev_out_value
375
+ end
376
+
356
377
  # Redeem script for P2SH output. To spend from a P2SH output, you need to provide
357
378
  # the script with a hash matching the P2SH address.
358
379
  def redeem_script script
@@ -394,6 +415,10 @@ module Bitcoin
394
415
  @key && (has_multiple_keys? ? @key.all?(&:priv) : @key.priv)
395
416
  end
396
417
 
418
+ def is_witness_v0_keyhash?
419
+ @prev_out_script && Script.new(@prev_out_script).is_witness_v0_keyhash?
420
+ end
421
+
397
422
  def sign(sig_hash)
398
423
  if has_multiple_keys?
399
424
  @key.map {|k| k.sign(sig_hash) }
@@ -438,7 +463,7 @@ module Bitcoin
438
463
  # o.script {|s| s.recipient address }
439
464
  # end
440
465
  #
441
- # t.output {|o| o.to "deadbeef", OP_RETURN }
466
+ # t.output {|o| o.to "deadbeef", :op_return }
442
467
  class TxOutBuilder
443
468
  attr_reader :txout
444
469
 
@@ -0,0 +1,191 @@
1
+ # encoding: ascii-8bit
2
+
3
+ module Bitcoin
4
+
5
+ def self.hmac_sha512(key, data)
6
+ OpenSSL::HMAC.digest(OpenSSL::Digest.new('SHA512'), key, data)
7
+ end
8
+
9
+ # Integers modulo the order of the curve(secp256k1)
10
+ CURVE_ORDER = 115792089237316195423570985008687907852837564279074904382605163141518161494337
11
+
12
+ # BIP32 Extended private key
13
+ class ExtKey
14
+
15
+ attr_accessor :depth
16
+ attr_accessor :number
17
+ attr_accessor :chain_code
18
+ attr_accessor :priv_key
19
+ attr_accessor :parent_fingerprint
20
+
21
+ # generate master key from seed.
22
+ def self.generate_master(seed)
23
+ key = ExtKey.new
24
+ key.depth = key.number = 0
25
+ key.parent_fingerprint = '00000000'
26
+ l = Bitcoin.hmac_sha512('Bitcoin seed', seed)
27
+ left = OpenSSL::BN.from_hex(l[0..31].bth).to_i
28
+ raise 'invalid key' if left >= CURVE_ORDER || left == 0
29
+ key.priv_key = Bitcoin::Key.new(l[0..31].bth)
30
+ key.chain_code = l[32..-1]
31
+ key
32
+ end
33
+
34
+ # get ExtPubkey from priv_key
35
+ def ext_pubkey
36
+ k = ExtPubkey.new
37
+ k.depth = depth
38
+ k.number = number
39
+ k.parent_fingerprint = parent_fingerprint
40
+ k.chain_code = chain_code
41
+ key = Bitcoin::Key.new(nil, priv_key.pub, compressed: true)
42
+ k.pub_key = key.key.public_key
43
+ k
44
+ end
45
+
46
+ # serialize extended private key
47
+ def to_payload
48
+ Bitcoin.network[:extended_privkey_version].htb << [depth].pack('C') << parent_fingerprint.htb << [number].pack('N') << chain_code << [0x00].pack('C') << priv_key.priv.htb
49
+ end
50
+
51
+ # Base58 encoded extended private key
52
+ def to_base58
53
+ h = to_payload.bth
54
+ hex = h + Bitcoin.checksum(h)
55
+ Bitcoin.encode_base58(hex)
56
+ end
57
+
58
+ # get private key(hex)
59
+ def priv
60
+ priv_key.priv
61
+ end
62
+
63
+ # get public key(hex)
64
+ def pub
65
+ priv_key.pub
66
+ end
67
+
68
+ # get address
69
+ def addr
70
+ priv_key.addr
71
+ end
72
+
73
+ # get key identifier
74
+ def identifier
75
+ Bitcoin.hash160(priv_key.pub)
76
+ end
77
+
78
+ # get fingerprint
79
+ def fingerprint
80
+ identifier.slice(0..7)
81
+ end
82
+
83
+ # derive new key
84
+ def derive(number)
85
+ new_key = ExtKey.new
86
+ new_key.depth = depth + 1
87
+ new_key.number = number
88
+ new_key.parent_fingerprint = fingerprint
89
+ if number > (2**31 -1)
90
+ data = [0x00].pack('C') << priv_key.priv.htb << [number].pack('N')
91
+ else
92
+ data = priv_key.pub.htb << [number].pack('N')
93
+ end
94
+ l = Bitcoin.hmac_sha512(chain_code, data)
95
+ left = OpenSSL::BN.from_hex(l[0..31].bth).to_i
96
+ raise 'invalid key' if left >= CURVE_ORDER
97
+ child_priv = OpenSSL::BN.new((left + OpenSSL::BN.from_hex(priv_key.priv).to_i) % CURVE_ORDER)
98
+ raise 'invalid key ' if child_priv.to_i >= CURVE_ORDER
99
+ new_key.priv_key = Bitcoin::Key.new(child_priv.to_hex.rjust(64, '0'))
100
+ new_key.chain_code = l[32..-1]
101
+ new_key
102
+ end
103
+
104
+ # import private key from Base58 private key address
105
+ def self.from_base58(address)
106
+ data = StringIO.new(Bitcoin.decode_base58(address).htb)
107
+ key = ExtKey.new
108
+ data.read(4).bth # version
109
+ key.depth = data.read(1).unpack('C').first
110
+ key.parent_fingerprint = data.read(4).bth
111
+ key.number = data.read(4).unpack('N').first
112
+ key.chain_code = data.read(32)
113
+ data.read(1) # 0x00
114
+ key.priv_key = Bitcoin::Key.new(data.read(32).bth)
115
+ key
116
+ end
117
+
118
+ end
119
+
120
+ # BIP-32 Extended public key
121
+ class ExtPubkey
122
+ attr_accessor :depth
123
+ attr_accessor :number
124
+ attr_accessor :chain_code
125
+ attr_accessor :pub_key
126
+ attr_accessor :parent_fingerprint
127
+
128
+ # serialize extended pubkey
129
+ def to_payload
130
+ Bitcoin.network[:extended_pubkey_version].htb << [depth].pack('C') << parent_fingerprint.htb << [number].pack('N') << chain_code << pub.htb
131
+ end
132
+
133
+ # get public key(hex)
134
+ def pub
135
+ pub_key.group.point_conversion_form = :compressed
136
+ pub_key.to_hex.rjust(66, '0')
137
+ end
138
+
139
+ # get address
140
+ def addr
141
+ Bitcoin.hash160_to_address(Bitcoin.hash160(pub))
142
+ end
143
+
144
+ # get key identifier
145
+ def identifier
146
+ Bitcoin.hash160(pub)
147
+ end
148
+
149
+ # get fingerprint
150
+ def fingerprint
151
+ identifier.slice(0..7)
152
+ end
153
+
154
+ # Base58 encoded extended pubkey
155
+ def to_base58
156
+ h = to_payload.bth
157
+ hex = h + Bitcoin.checksum(h)
158
+ Bitcoin.encode_base58(hex)
159
+ end
160
+
161
+ # derive child key
162
+ def derive(number)
163
+ new_key = ExtPubkey.new
164
+ new_key.depth = depth + 1
165
+ new_key.number = number
166
+ new_key.parent_fingerprint = fingerprint
167
+ raise 'hardened key is not support' if number > (2**31 -1)
168
+ data = pub.htb << [number].pack('N')
169
+ l = Bitcoin.hmac_sha512(chain_code, data)
170
+ left = OpenSSL::BN.from_hex(l[0..31].bth)
171
+ raise 'invalid key' if left.to_i >= CURVE_ORDER
172
+ new_key.pub_key = Bitcoin.bitcoin_elliptic_curve.group.generator.mul(left).ec_add(pub_key)
173
+ new_key.chain_code = l[32..-1]
174
+ new_key
175
+ end
176
+
177
+ # import private key from Base58 private key address
178
+ def self.from_base58(address)
179
+ data = StringIO.new(Bitcoin.decode_base58(address).htb)
180
+ key = ExtPubkey.new
181
+ data.read(4).bth # version
182
+ key.depth = data.read(1).unpack('C').first
183
+ key.parent_fingerprint = data.read(4).bth
184
+ key.number = data.read(4).unpack('N').first
185
+ key.chain_code = data.read(32)
186
+ key.pub_key = OpenSSL::PKey::EC::Point.from_hex(Bitcoin.bitcoin_elliptic_curve.group, data.read(33).bth)
187
+ key
188
+ end
189
+ end
190
+
191
+ end