hdet-ec-key 1.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: fcb033ee0743d2afde945e3751dcd25cdad97aa555b96eb1fe5019b8cc209005
4
+ data.tar.gz: 00e91fabee3f2e82741a7e51ed2906c2952aa03e5bf949e6693cd728470052a4
5
+ SHA512:
6
+ metadata.gz: 9ded3bcc9aee7b1c52519ee9da5650b26ddcc10e05d1c3dc1c4316c36491dfe7295bfd5034a971fed7ce8ecbae51852791859c2dda8e175a229f942476a1057a
7
+ data.tar.gz: dda56d995c45047886960bb2424e1fa6fea72ec8a42a45fae2b6f05a20d89e73f18be9cecfcc9e9564a8319ae94bbdcae8b2646fbee9cadda4938add23aae673
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 Stéphane Clérambault
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.adoc ADDED
@@ -0,0 +1,88 @@
1
+ = HDetEcKey
2
+
3
+ HDetEcKey is a Ruby implement of https://en.bitcoin.it/wiki/Deterministic_Wallet[Hierachical Deterministic wallets] according to the specification https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP0032].
4
+
5
+ == What is use for ?
6
+
7
+
8
+ == How to get it !
9
+
10
+ [source, bash]
11
+ ----
12
+ git clone https://gitlab.com/elionne/hdet-ec-key.git
13
+ ----
14
+
15
+ Or in a neer future:
16
+
17
+ [source, bash]
18
+ ----
19
+ gem install hdet-ec-key
20
+ ----
21
+
22
+ === Prerequistes
23
+
24
+ - Ruby 2.5.0
25
+ - Openssl (ruby included)
26
+
27
+ Support of other version of Ruby should be possible.
28
+
29
+ == Usage
30
+
31
+ For more details about how Herarchical Deterministic Wallet works, please see the https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki[BIP0032] specifications.
32
+
33
+ === Generate master key
34
+
35
+ First you need to generate the first key.
36
+ The point where all keys starts.
37
+ The BIP32 standard specify simple method with `HMAC512`.
38
+ The default key is _Bitcoin seed_ and the seed is choosen by the user.
39
+
40
+ To generate a master key:
41
+
42
+ [source, ruby]
43
+ ----
44
+ seed = ["000102030405060708090a0b0c0d0e0f"].pack("H*")
45
+ master_key = HDetEc::Key.generate_master_key(seed)
46
+ ----
47
+
48
+ No convertion of the given string is operated, the `seed` is used _as is_.
49
+ The `key` can be change by setting the second parameter:
50
+
51
+ [source, ruby]
52
+ ----
53
+ master_key = HDetEc::Key.generate_master_key(seed, "my special key")
54
+ ----
55
+
56
+ === Public key, private and public derivation
57
+
58
+ To get the public key from the a key:
59
+
60
+ [source, ruby]
61
+ ----
62
+ pub = master_key.public_key
63
+
64
+ # or a more verbose version
65
+ pub = master.public_key_from_private
66
+ ----
67
+
68
+ Then you can derive, ether public or private key:
69
+
70
+ [source, ruby]
71
+ ----
72
+ pub.derive([0, 1, 2000])
73
+ master_key.derive(0, 1.h, 2000)
74
+ ----
75
+
76
+ The _derivation path_ is determined by an array of index.
77
+ the _#h_ function for interger means *harderned* index, only available for private key derivation.
78
+
79
+ === Serialization
80
+
81
+ Each keys can be serialized in BIP32 specified form:
82
+
83
+ [source,ruby]
84
+ ----
85
+ pub.serialize # xpub.....
86
+ master_key.serialize # xprv.....
87
+ ----
88
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0
@@ -0,0 +1,18 @@
1
+ require 'openssl'
2
+ require 'digest'
3
+ require 'base58'
4
+
5
+ # @abstract Use for hardened index
6
+ #
7
+ # @return the self plus 2^31
8
+ class Integer
9
+ def h
10
+ 2**31 + self
11
+ end
12
+ end
13
+
14
+ module HDetEc
15
+ autoload :DataManipulation, 'hdet-ec-key/data_manipulation'
16
+ autoload :ECManipulation, 'hdet-ec-key/ec_manipulation'
17
+ autoload :Key, 'hdet-ec-key/key'
18
+ end
@@ -0,0 +1,60 @@
1
+
2
+ module HDetEc
3
+ module DataManipulation
4
+
5
+ # Serialize a 32-bit unsigned integer i as a 4-byte sequence, most
6
+ # significant byte first.
7
+ def ser32(i)
8
+ [i].pack("l>")
9
+ end
10
+
11
+ def to_binary(num, byte_length=1)
12
+ num = num.to_i if num.kind_of? OpenSSL::BN
13
+
14
+ hex_num = num.to_s(16).rjust(byte_length*2, "0")
15
+ hex_num = "0" + hex_num if hex_num.size.odd?
16
+
17
+ bin = [hex_num].pack("H*")
18
+ end
19
+
20
+ def to_number(bin)
21
+ bin.unpack1("H*").to_i 16
22
+ end
23
+
24
+ # Serializes the integer p as a 32-byte sequence, most significant byte first.
25
+ def ser256(p)
26
+ raise ArgumentError, "overflow" if p.bit_length > 256
27
+ to_binary(p, 256/8)
28
+ end
29
+
30
+ def left_hash(h)
31
+ h[0..31]
32
+ end
33
+
34
+ def right_hash(h)
35
+ h[32..63]
36
+ end
37
+
38
+ def split_hash(h)
39
+ [left_hash(h), right_hash(h)]
40
+ end
41
+
42
+ def rmd160_sha256(data)
43
+ Digest::RMD160.digest Digest::SHA256.digest(data)
44
+ end
45
+
46
+ alias :hash160 :rmd160_sha256
47
+
48
+ def double_sha256(data)
49
+ Digest::SHA256.digest Digest::SHA256.digest(data)
50
+ end
51
+
52
+ alias :hash256 :double_sha256
53
+
54
+
55
+ # Interprets a 32-byte sequence as a 256-bit number, most significant byte
56
+ # first.
57
+ alias :parse256 :to_number
58
+
59
+ end
60
+ end
@@ -0,0 +1,133 @@
1
+ require 'openssl'
2
+
3
+ module HDetEc
4
+ module ECManipulation
5
+
6
+ # The default group is the same as used by Bitcoin network, "secp256k1". But
7
+ # it can be change, just provide the a OpenSSL group string.
8
+ #
9
+ # example:
10
+ # HDetEc::Key.group = "secp256r1"
11
+ #
12
+ def group=(openssl_group)
13
+ @@group = OpenSSL::PKey::EC.new(openssl_group).group
14
+ @@group.point_conversion_form = :compressed
15
+ @@group
16
+ end
17
+
18
+ def group
19
+ @@group ||= OpenSSL::PKey::EC.new("secp256k1").group
20
+ @@group.point_conversion_form = :compressed
21
+ @@group
22
+ end
23
+
24
+ # @abstract Generate public key from the private one
25
+ #
26
+ # The input parameter {#private_key} a binary string representation of the
27
+ # private key
28
+ #
29
+ # @return [String] The output is a binary representation of the public key
30
+ # in the compressed form.
31
+ def generate_public_key_from_private(private_key)
32
+ bn_private_key = OpenSSL::BN.new private_key, 2
33
+
34
+ public_key = group.generator.mul(bn_private_key)
35
+ public_key.to_bn.to_s(2)
36
+ end
37
+
38
+ alias_method :point, :generate_public_key_from_private
39
+
40
+ # @abstract Generic derivation method for hardened keys
41
+ #
42
+ # Alogrithm to derive only hardened private key. That means no {#index}
43
+ # below 2^31 are allowed.
44
+ def child_key_derivation_hardened(extended_key, index)
45
+ k, c = extended_key
46
+ key = c
47
+ data = "\x00" + k + ser32(index)
48
+ OpenSSL::HMAC.digest('SHA512', key, data)
49
+ end
50
+
51
+ alias_method :CKDh, :child_key_derivation_hardened
52
+
53
+ # @abstract Generic derivation method for non-hardened keys
54
+ #
55
+ # Alogrithm to derive public and private key if it not hardened key. That
56
+ # means no {#index} above 2^31 - 1 are allowed.
57
+ def child_key_derivation(extended_key, index)
58
+ k, c = extended_key
59
+ key = c
60
+ data = serp(k) + ser32(index)
61
+ OpenSSL::HMAC.digest('SHA512', key, data)
62
+ end
63
+
64
+ alias_method :CKD, :child_key_derivation
65
+
66
+ # @abstract Derive a public key according to BIP32 specifications
67
+ #
68
+ # Public parent key → public child key.
69
+ #
70
+ # @param extended_key [Array] extended_key
71
+ # @param index [Integer]
72
+ #
73
+ # @note
74
+ # https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#private-parent-key--private-child-key
75
+ def child_key_derivation_private(extended_key, index)
76
+ k, c = extended_key
77
+
78
+ if index >= 2**31
79
+ inter = child_key_derivation_hardened(extended_key, index)
80
+ else
81
+ inter = child_key_derivation([point(k), c], index)
82
+ end
83
+
84
+ iL, iR = split_hash(inter)
85
+
86
+ bn = parse256(iL) + parse256(k)
87
+ k_child = OpenSSL::BN.new(bn) % group.order
88
+
89
+ [k_child.to_s(2), iR]
90
+ end
91
+
92
+ alias_method :CKDpriv, :child_key_derivation_private
93
+
94
+
95
+ # @abstract Derive a public key according to BIP32 specifications
96
+ #
97
+ # Public parent key → public child key.
98
+ #
99
+ # @param extended_key [Array] extended_key
100
+ # @param index [Integer]
101
+ #
102
+ # @note
103
+ # https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#public-parent-key--public-child-key
104
+ def child_key_derivation_public(extended_key, index)
105
+ k, c = extended_key
106
+ if index >= 2**31
107
+ raise "No hardened public key derivation possible"
108
+ end
109
+
110
+ inter = child_key_derivation(extended_key, index)
111
+
112
+ iL, iR = split_hash(inter)
113
+
114
+ bn = OpenSSL::BN.new(iL, 2)
115
+ pub_key = OpenSSL::PKey::EC::Point.new(group, k).mul(1, bn)
116
+ [pub_key.to_octet_string(:compressed), iR]
117
+ end
118
+
119
+ alias_method :CKDpub, :child_key_derivation_public
120
+
121
+ # Serializes the coordinate pair P = (x,y) as a byte sequence using SEC1's
122
+ # compressed form: (0x02 or 0x03) || ser256(x), where the header byte
123
+ # depends on the parity of the omitted y coordinate.
124
+ # input kp: public key
125
+ def serp(kp)
126
+ # ensure kp is in compressed form
127
+ point = OpenSSL::PKey::EC::Point.new(group, kp)
128
+ point.to_octet_string(:compressed)
129
+ end
130
+
131
+ end
132
+ end
133
+
@@ -0,0 +1,198 @@
1
+
2
+ module HDetEc
3
+
4
+ class Key
5
+
6
+ class << self
7
+ include DataManipulation
8
+ include ECManipulation
9
+
10
+ # @abstract Generate a master extended key from {#seed} and {#key}
11
+ #
12
+ # According to BIP32 specification, the master key is generated from a
13
+ # HMAC512 and splited in two vector of 256 bits each. The first is the
14
+ # master key and the later is the chain code.
15
+ #
16
+ # You just have to provide the {#seed}. The standard {#key} is
17
+ # "Bitcoin seed" but it can be overrided.
18
+ #
19
+ # The {#seed} could be any kind of string. No convertion is performed (idem
20
+ # for {#key}), so if you want a binary seed represented by the hex form you
21
+ # have to convert it like:
22
+ #
23
+ # @example: Binary seed with hex form
24
+ # seed = ["000102030405060708090a0b0c0d0e0f"].pack("H*")
25
+ #
26
+ #
27
+ # @return [Key] A new master key.
28
+ # @note
29
+ # https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#master-key-generation
30
+ def generate_master_key(seed, key = "Bitcoin seed")
31
+ inter = OpenSSL::HMAC.digest('SHA512', key, seed)
32
+ m = split_hash(inter)
33
+ Key.new(m, :private, 0, 0, "\x00" * 4)
34
+ end
35
+
36
+
37
+ # @abstract Import a serialized key in as describe in BIP32 specification
38
+ #
39
+ # Conforming to the BIP32 specification, the key should start with 'xpub'
40
+ # or 'xprv'. Other version of testnet is not supported.
41
+ #
42
+ # Because a checksum is stored in the key, this function check the
43
+ # integrety of imported data, if check sum could not be verified it raise
44
+ # an ArgumentError "Wrong checksum".
45
+ def import(serialized_key_string)
46
+ data = Base58.base58_to_binary(serialized_key_string, :bitcoin)
47
+
48
+ unpacked = data.unpack("L>Ca4L>a32a33a4")
49
+ version, depth, parent_fingerprint, index, c, k, checksum = unpacked
50
+
51
+ # Remove checksum from data
52
+ data.slice!(-4..-1)
53
+
54
+ raise ArgumentError, "Wrong checksum" unless hash256(data)[0..3] == checksum
55
+
56
+ case version
57
+ when 0x0488ADE4
58
+ k.slice!(0)
59
+ Key.new([k, c], :private, depth, index, parent_fingerprint)
60
+ when 0x0488B21E
61
+ Key.new([k, c], :public, depth, index, parent_fingerprint)
62
+ else raise "version not supported #{version.to_s(16)}"
63
+ end
64
+
65
+ end
66
+ end
67
+
68
+ include DataManipulation
69
+
70
+ attr_reader :key, :depth, :index, :public_or_private, :chain_code
71
+ attr_reader :parent_fingerprint, :fingerprint
72
+
73
+ def initialize(extended_k, public_or_private, depth, index, parent_fingerprint)
74
+ @depth = depth
75
+ @index = index
76
+ @key, @chain_code = extended_k
77
+ @public_or_private = public_or_private
78
+ @parent_fingerprint = parent_fingerprint
79
+ end
80
+
81
+ def public_key
82
+ case public_or_private
83
+ when :public
84
+ self
85
+ when :private
86
+ Key.new([Key.point(key), chain_code], :public, depth, index, parent_fingerprint)
87
+ end
88
+ end
89
+
90
+ alias_method :public_key_from_private, :public_key
91
+
92
+ def public?
93
+ public_or_private == :public
94
+ end
95
+
96
+ def private?
97
+ public_or_private == :private
98
+ end
99
+
100
+
101
+ # @abstract serialize key according to BIP32 specifications
102
+ #
103
+ # The serialization result is a string in Base58 that represents the public
104
+ # or the private extented key.
105
+ #
106
+ # @note
107
+ # https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format
108
+ def serialize_extended_key
109
+ k, c = key, chain_code
110
+
111
+ case public_or_private
112
+ when :private
113
+ data = ser32(0x0488ADE4) + to_binary(depth) + parent_fingerprint + ser32(index) + c + "\x00" + k
114
+ when :public then
115
+ data = ser32(0x0488B21E) + to_binary(depth) + parent_fingerprint + ser32(index) + c + Key.serp(public_key.key)
116
+ else raise "invalid property #{public_or_private}"
117
+ end
118
+
119
+ Base58.binary_to_base58(data + hash256(data)[0..3], :bitcoin)
120
+ end
121
+
122
+ alias_method :serialize, :serialize_extended_key
123
+
124
+ # @abstract Derive key by specifying derivation path
125
+ #
126
+ # Derive a private or a public key by applying multiple derivation definined
127
+ # by the derivation path.
128
+ #
129
+ # The {#path} is an Array of multiple indexes. For example to derive a key
130
+ # like m/0H/1/2H/2, use indexes [0.h, 1, 2.h, 2].
131
+ #
132
+ # @return [HDetEc] The returned object is an instance of the new derivated
133
+ # key.
134
+ #
135
+ # @note
136
+ # https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#the-key-tree
137
+ def derive(path)
138
+ return self if path.nil? || path.empty?
139
+
140
+ case public_or_private
141
+ when :public then public_key_derivation(path)
142
+ when :private then private_key_derivation(path)
143
+ else
144
+ raise "Wrong key"
145
+ end
146
+ end
147
+
148
+ # @abstract Compute the key fingerprint
149
+ #
150
+ # @note
151
+ # https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#key-identifiers
152
+ def fingerprint
153
+ key_fingerprint([key, chain_code])
154
+ end
155
+
156
+ def public_key_derivation(path)
157
+ ex_k = [key, chain_code]
158
+
159
+ ex_k_parent = ex_k
160
+ path.each do |i|
161
+ raise "No hardened derivation for public key" if i >= 2**31
162
+ ex_k_parent = ex_k
163
+ ex_k = HDetEc::Key.CKDpub(ex_k_parent, i)
164
+ end
165
+
166
+ HDetEc::Key.new(ex_k, :public, path.size, path.last, key_fingerprint(ex_k_parent))
167
+ end
168
+
169
+ private :public_key_derivation
170
+
171
+ def private_key_derivation(path)
172
+ ex_k = [key, chain_code]
173
+
174
+ ex_k_parent = ex_k
175
+ path.each do |i|
176
+ ex_k_parent = ex_k
177
+ ex_k = HDetEc::Key.CKDpriv(ex_k_parent, i)
178
+ end
179
+
180
+ HDetEc::Key.new(ex_k, :private, path.size, path.last, key_fingerprint(ex_k_parent))
181
+ end
182
+
183
+ private :private_key_derivation
184
+
185
+ def key_fingerprint(ex_k)
186
+ k, c = ex_k
187
+ case public_or_private
188
+ when :private then hash160(Key::point(k))[0..3]
189
+ when :public then hash160(k)[0..3]
190
+ end
191
+ end
192
+
193
+ private :key_fingerprint
194
+
195
+ end
196
+
197
+
198
+ end
@@ -0,0 +1,39 @@
1
+ require 'hdet-ec-key'
2
+
3
+ require_relative 'test_vectors.rb'
4
+
5
+ shared_examples "public and private key derivation" do |vector|
6
+ m = HDetEc::Key.generate_master_key([vector[:seed]].pack("H*"))
7
+ vector[:data].each do |data|
8
+
9
+ it data[:chain] + " private key" do
10
+ expect(m.derive(data[:path]).serialize).to eq(data[:priv_key])
11
+ end
12
+
13
+ it data[:chain] + " public key" do
14
+ expect(m.derive(data[:path]).public_key.serialize).to eq(data[:pub_key])
15
+ end
16
+
17
+ if data[:path].all? {|i| i < 2**31 }
18
+ it data[:chain] + " public key derivation" do
19
+ expect(m.public_key.derive(data[:path]).serialize).to eq(data[:pub_key])
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+
26
+ describe "derivation key seed = 000102030405060708090a0b0c0d0e0f" do
27
+ vector = test_vector[0]
28
+ include_examples("public and private key derivation", vector)
29
+ end
30
+
31
+ describe "derivation key seed = fffcf9f6f3f0edeae7e4e1...484542" do
32
+ vector = test_vector[1]
33
+ include_examples("public and private key derivation", vector)
34
+ end
35
+
36
+ describe "derivation key seed = 4b381541583be4...3235be" do
37
+ vector = test_vector[2]
38
+ include_examples("public and private key derivation", vector)
39
+ end
@@ -0,0 +1,78 @@
1
+ require 'hdet-ec-key/data_manipulation'
2
+ require 'openssl'
3
+
4
+ describe HDetEc::DataManipulation do
5
+ let(:dm) {Class.new {include HDetEc::DataManipulation}.new }
6
+
7
+ context "#ser32" do
8
+ it "serialize 4 bytes length" do
9
+ expect(dm.ser32(0)).to eq("\x00\x00\x00\x00".b)
10
+ end
11
+
12
+ it "select least significant bytes when overflow" do
13
+ expect(dm.ser32(2**32+2)).to eq("\x00\x00\x00\x02".b)
14
+ end
15
+
16
+ it "serialize in big-endian" do
17
+ expect(dm.ser32(0xabcd)).to eq("\x00\x00\xab\xcd".b)
18
+ expect(dm.ser32(0xabcd00ef)).to eq("\xab\xcd\x00\xef".b)
19
+ end
20
+ end
21
+
22
+ context "#to_binary" do
23
+ it "autodetect bytes length" do
24
+ i2_38_33 = dm.to_binary(2**39 + 2**33)
25
+ expect(i2_38_33.size).to be 5
26
+ expect(i2_38_33).to eq("\x82\x00\x00\x00\x00".b)
27
+
28
+ i2_15 = dm.to_binary(2**15)
29
+ expect(i2_15.size).to be 2
30
+ expect(i2_15).to eq("\x80\x00".b)
31
+ end
32
+
33
+ it "left pad with 0" do
34
+ i2_33 = dm.to_binary(2**33)
35
+ expect(i2_33).to eq("\x02\x00\x00\x00\x00".b)
36
+
37
+ i2_127 = dm.to_binary(2**128)
38
+ expect(i2_127).to eq("\x01".b + "\x00".b * 16)
39
+ end
40
+
41
+ it "serialize in big-endian" do
42
+ expect(dm.to_binary(0x0123abcdef)).to eq("\x01\x23\xab\xcd\xef".b)
43
+ expect(dm.to_binary(0xabcd)).to eq("\xab\xcd".b)
44
+ end
45
+
46
+ it "could specify serialize byte size" do
47
+ expect(dm.to_binary(0, 8).size).to eq 8
48
+ expect(dm.to_binary(2**128-1, 2).size).to eq 16
49
+ end
50
+ end
51
+
52
+ context "#to_number" do
53
+ it "could convert number" do
54
+ expect(dm.to_number("\x01".b + "\x00".b * 16)).to eq(2**128)
55
+ end
56
+
57
+ it "deserialize in big-endian" do
58
+ expect(dm.to_number("\x01\x23\xab\xcd\xef")).to eq(0x0123abcdef)
59
+ expect(dm.to_number("\xab\xcd")).to eq(0xabcd)
60
+ end
61
+ end
62
+
63
+ context "#ser256" do
64
+ it "always 256 bits long (32 bytes)" do
65
+ expect(dm.ser256(1).size).to eq(32)
66
+ expect(dm.ser256(73927273964184847435527778358743529074551849469743120849858998083583072341153).size).to eq(32)
67
+ end
68
+
69
+ it "raise ArgumentError exception when overflow" do
70
+ expect{dm.ser256(2**256)}.to raise_error(ArgumentError)
71
+ end
72
+
73
+ it "serialize in big-endian" do
74
+ expect(dm.ser256(0x0123abcdef)).to eq( "\x00".b * 27 + "\x01\x23\xab\xcd\xef".b)
75
+ end
76
+ end
77
+
78
+ end
data/spec/key_spec.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'hdet-ec-key'
2
+
3
+ describe HDetEc::Key do
4
+ context "#import" do
5
+ it "can import private key from BIP32 string" do
6
+ bip32_key = "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"
7
+ key = HDetEc::Key.import(bip32_key)
8
+
9
+ expect(key.serialize).to eq(bip32_key)
10
+ end
11
+
12
+ it "can import public key from BIP32 string" do
13
+ bip32_key = "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
14
+ key = HDetEc::Key.import(bip32_key)
15
+
16
+ expect(key.serialize).to eq(bip32_key)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,82 @@
1
+ def test_vector
2
+ [{
3
+ seed: "000102030405060708090a0b0c0d0e0f",
4
+ data: [{
5
+ chain: "Chain m",
6
+ path: [],
7
+ pub_key: "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8",
8
+ priv_key: "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi",
9
+ }, {
10
+ chain: "Chain m/0h",
11
+ path: [0.h],
12
+ pub_key: "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw",
13
+ priv_key: "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7",
14
+ },{
15
+ chain: "Chain m/0h/1",
16
+ path: [0.h, 1],
17
+ pub_key: "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ",
18
+ priv_key: "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs",
19
+ }, {
20
+ chain: "Chain m/0h/1/2h",
21
+ path: [0.h, 1, 2.h],
22
+ pub_key: "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5",
23
+ priv_key: "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM",
24
+ }, {
25
+ chain: "Chain m/0h/1/2h/2",
26
+ path: [0.h, 1, 2.h, 2],
27
+ pub_key: "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV",
28
+ priv_key: "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334",
29
+ }, {
30
+ chain: "Chain m/0h/1/2h/2/1000000000",
31
+ path: [0.h, 1, 2.h, 2, 1000000000],
32
+ pub_key: "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy",
33
+ priv_key: "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76",
34
+ }]
35
+ }, {
36
+ seed: "fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542",
37
+ data: [{
38
+ chain: "Chain m",
39
+ path: [],
40
+ pub_key: "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB",
41
+ priv_key: "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U",
42
+ }, {
43
+ chain: "Chain m/0",
44
+ path: [0],
45
+ pub_key: "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH",
46
+ priv_key: "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt",
47
+ }, {
48
+ chain: "Chain m/0/2147483647h",
49
+ path: [0, 2147483647.h],
50
+ pub_key: "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a",
51
+ priv_key: "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9",
52
+ }, {
53
+ chain: "Chain m/0/2147483647h/1",
54
+ path: [0, 2147483647.h, 1],
55
+ pub_key: "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon",
56
+ priv_key: "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef",
57
+ }, {
58
+ chain: "Chain m/0/2147483647h/1/2147483646h",
59
+ path: [0, 2147483647.h, 1, 2147483646.h],
60
+ pub_key: "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL",
61
+ priv_key: "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc",
62
+ }, {
63
+ chain: "Chain m/0/2147483647h/1/2147483646h/2",
64
+ path: [0, 2147483647.h, 1, 2147483646.h, 2],
65
+ pub_key: "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt",
66
+ priv_key: "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j",
67
+ }]
68
+ }, {
69
+ seed: "4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be",
70
+ data: [{
71
+ chain: "Chain m",
72
+ path: [],
73
+ pub_key: "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13",
74
+ priv_key: "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6",
75
+ }, {
76
+ chain: "Chain m/0h",
77
+ path: [0.h],
78
+ pub_key: "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y",
79
+ priv_key: "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L",
80
+ }]
81
+ }]
82
+ end
metadata ADDED
@@ -0,0 +1,87 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: hdet-ec-key
3
+ version: !ruby/object:Gem::Version
4
+ version: '1.0'
5
+ platform: ruby
6
+ authors:
7
+ - Stéphane Clérambault
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: base58
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rspec
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: |-
42
+ Provide an easy implementation of the BIP32
43
+ specification. This implementation is not related to
44
+ bitcoin and can be use for any propose.
45
+ email:
46
+ - stephane@clerambault.fr
47
+ executables: []
48
+ extensions: []
49
+ extra_rdoc_files:
50
+ - README.adoc
51
+ files:
52
+ - LICENSE
53
+ - README.adoc
54
+ - VERSION
55
+ - lib/hdet-ec-key.rb
56
+ - lib/hdet-ec-key/data_manipulation.rb
57
+ - lib/hdet-ec-key/ec_manipulation.rb
58
+ - lib/hdet-ec-key/key.rb
59
+ - spec/bip32_vector_spec.rb
60
+ - spec/data_manipulation_spec.rb
61
+ - spec/key_spec.rb
62
+ - spec/test_vectors.rb
63
+ homepage: https://gitlab.com/elionne/hdet-ec-key
64
+ licenses:
65
+ - MIT
66
+ metadata: {}
67
+ post_install_message:
68
+ rdoc_options: []
69
+ require_paths:
70
+ - lib
71
+ required_ruby_version: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: 2.5.0
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ requirements: []
82
+ rubyforge_project:
83
+ rubygems_version: 2.7.6
84
+ signing_key:
85
+ specification_version: 4
86
+ summary: Implements Herarchical Deterministic Key wallet.
87
+ test_files: []