hdet-ec-key 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +21 -0
- data/README.adoc +88 -0
- data/VERSION +1 -0
- data/lib/hdet-ec-key.rb +18 -0
- data/lib/hdet-ec-key/data_manipulation.rb +60 -0
- data/lib/hdet-ec-key/ec_manipulation.rb +133 -0
- data/lib/hdet-ec-key/key.rb +198 -0
- data/spec/bip32_vector_spec.rb +39 -0
- data/spec/data_manipulation_spec.rb +78 -0
- data/spec/key_spec.rb +19 -0
- data/spec/test_vectors.rb +82 -0
- metadata +87 -0
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
|
data/lib/hdet-ec-key.rb
ADDED
@@ -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: []
|