bitcoin-ruby 0.0.10 → 0.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/COPYING +1 -1
- data/Gemfile.lock +9 -10
- data/README.rdoc +1 -1
- data/Rakefile +4 -2
- data/lib/bitcoin.rb +4 -2
- data/lib/bitcoin/bloom_filter.rb +125 -0
- data/lib/bitcoin/builder.rb +34 -9
- data/lib/bitcoin/ext_key.rb +191 -0
- data/lib/bitcoin/ffi/openssl.rb +1 -1
- data/lib/bitcoin/key.rb +6 -4
- data/lib/bitcoin/protocol.rb +13 -11
- data/lib/bitcoin/protocol/block.rb +38 -2
- data/lib/bitcoin/protocol/parser.rb +8 -0
- data/lib/bitcoin/protocol/partial_merkle_tree.rb +61 -0
- data/lib/bitcoin/protocol/script_witness.rb +31 -0
- data/lib/bitcoin/protocol/tx.rb +170 -10
- data/lib/bitcoin/protocol/txin.rb +8 -0
- data/lib/bitcoin/protocol/version.rb +2 -1
- data/lib/bitcoin/script.rb +58 -8
- data/lib/bitcoin/version.rb +1 -1
- data/spec/bitcoin/bloom_filter_spec.rb +23 -0
- data/spec/bitcoin/builder_spec.rb +12 -0
- data/spec/bitcoin/ext_key_spec.rb +180 -0
- data/spec/bitcoin/fixtures/filteredblock-0.bin +0 -0
- data/spec/bitcoin/fixtures/rawblock-testnet-1151351.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-p2wpkh.bin +0 -0
- data/spec/bitcoin/fixtures/rawtx-p2wpkh.json +67 -0
- data/spec/bitcoin/fixtures/tx-0a6a357e2f7796444e02638749d9611c008b253fb55f5dc88b739b230ed0c4c3.json +139 -0
- data/spec/bitcoin/fixtures/tx-28204cad1d7fc1d199e8ef4fa22f182de6258a3eaafe1bbe56ebdcacd3069a5f.json +34 -0
- data/spec/bitcoin/protocol/bip143_spec.rb +116 -0
- data/spec/bitcoin/protocol/block_spec.rb +27 -0
- data/spec/bitcoin/protocol/partial_merkle_tree_spec.rb +38 -0
- data/spec/bitcoin/protocol/tx_spec.rb +134 -1
- data/spec/bitcoin/script/script_spec.rb +53 -2
- metadata +27 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1103ce4c2cb0a379a5de5edfb2a9cfb0cfcb19e0
|
4
|
+
data.tar.gz: db4312fc6546cd7c357ab95fc2293814025f7f5c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2562f4d8edaa26237e99d775dc263127c98d31d9570359811a453c1076d017f0f41cc07fd64bb63f263d31e4d3ab0295ca53cb55ce31d75a6ba5ad25a0c54371
|
7
|
+
data.tar.gz: c5d9ceb1c9c253ce1b761343aa5fd665fb3eba9ffa423add7bea8d4f6bd0abfd6b23d3969c282a54fee21f31bb96897c74e5cf700c0823786847315617273dd9
|
data/.travis.yml
CHANGED
data/COPYING
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
Copyright (c)
|
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.
|
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.
|
11
|
-
ffi (1.9.
|
12
|
-
ffi-compiler (0.1
|
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.
|
16
|
-
rake (
|
17
|
-
scrypt (
|
18
|
-
ffi-compiler (>= 0.0
|
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.
|
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.
|
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
|
-
|
27
|
-
|
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
|
-
|
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
|
-
|
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
|
data/lib/bitcoin/builder.rb
CHANGED
@@ -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.
|
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
|
-
|
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
|
-
|
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",
|
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
|