money-tree 0.9.0 → 0.11.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: b30fcb1b0feac18dde7d94b62a0ff97666209d3f
4
- data.tar.gz: ffa45ca51da46d1d56d701f24e36d61848e261ea
2
+ SHA256:
3
+ metadata.gz: 040b86e807e6dea0ebf4dfc4b5e508e3f9ef673c94b95a210a18c125d8857974
4
+ data.tar.gz: '01800e0fb32058488fd01f1a8f2be3539788c7d0bebc0021fd49776fdde947a4'
5
5
  SHA512:
6
- metadata.gz: 859a1b5a4dfd21fafb73dba60ad6d243c3d8462bba900aed584934e463976fd0075abd5fa83b6317f54ce9801f342e3ba31ac639bb53ee9769068fd1f1cbda13
7
- data.tar.gz: 3609f386e6fc5276f41a51221898dc0ebb10d314fd4b76a6ea4522ced950738b064167fb0f4d6a7d84c1b12823dd255494043e817b9dd7562f3f3eb4c02e093d
6
+ metadata.gz: 70a6465799b1f7140a2e45c2ded416b41e4b64a7801b57325ed20cea470c92cad96e7644596d523a192254e7284eb772406e90af0a797f5b367304cbe2e9b725
7
+ data.tar.gz: 2d0c6bea3a33f161cf30a3400d3f75b6119098f035eca6cdac076c11c03596a2e0e205456b1e3ea2453f2ad1e58fd7edec91ff21ec1a8fd515c2d7836169762c
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: Spec
3
+
4
+ on:
5
+ pull_request:
6
+ branches:
7
+ - master
8
+ push:
9
+ branches:
10
+ - master
11
+
12
+ jobs:
13
+ test:
14
+ runs-on: ${{ matrix.os }}
15
+ strategy:
16
+ fail-fast: false
17
+ matrix:
18
+ os: [ubuntu-latest, macos-latest]
19
+ ruby: ['2.7', '3.0']
20
+ steps:
21
+ - uses: actions/checkout@v2
22
+ - uses: ruby/setup-ruby@v1
23
+ with:
24
+ ruby-version: ${{ matrix.ruby }}
25
+ bundler-cache: false
26
+ - name: Install Dependencies
27
+ run: |
28
+ bundle install
29
+ - name: Run Tests
30
+ run: |
31
+ bundle exec rspec
data/README.md CHANGED
@@ -1,13 +1,23 @@
1
- [![Build Status](https://travis-ci.org/GemHQ/money-tree.png)](https://travis-ci.org/GemHQ/money-tree) [![Coverage Status](https://img.shields.io/coveralls/GemHQ/money-tree.svg)](https://coveralls.io/r/GemHQ/money-tree?branch=master) [![Code Climate](https://codeclimate.com/github/GemHQ/money-tree.png)](https://codeclimate.com/github/GemHQ/money-tree) [![Gem Version](https://badge.fury.io/rb/money-tree.png)](http://badge.fury.io/rb/money-tree)
2
1
  # MoneyTree
2
+
3
+ [![GitHub release (latest by date)](https://img.shields.io/github/v/release/GemHQ/money-tree)](https://github.com/GemHQ/money-tree/releases)
4
+ [![Gem](https://img.shields.io/gem/v/money-tree)](https://rubygems.org/gems/money-tree)
5
+ [![Gem](https://img.shields.io/gem/dt/money-tree)](https://rubygems.org/gems/money-tree)
6
+ [![GitHub top language](https://img.shields.io/github/languages/top/GemHQ/money-tree?color=red)](https://github.com/GemHQ/money-tree/pulse)
7
+
8
+ [![GitHub Workflow Status](https://img.shields.io/github/workflow/status/GemHQ/money-tree/Spec)](https://github.com/GemHQ/money-tree/actions)
9
+ [![Coverage Status](https://img.shields.io/coveralls/GemHQ/money-tree.svg)](https://coveralls.io/r/GemHQ/money-tree?branch=master)
10
+ [![Code Climate](https://codeclimate.com/github/GemHQ/money-tree.png)](https://codeclimate.com/github/GemHQ/money-tree)
11
+ [![GitHub](https://img.shields.io/github/license/GemHQ/money-tree)](LICENSE)
12
+
3
13
  ### RSpec tested. Big Brother removed.
4
14
 
5
15
  MoneyTree is a Ruby implementation of Bitcoin Wallets. Specifically, it supports [Hierachical Deterministic wallets](https://en.bitcoin.it/wiki/Deterministic_Wallet) according to the protocol specified in [BIP0032](https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki).
6
16
 
7
17
  ___
8
- If you find this helpful, please consider a small Bitcoin donation to 1nj2kie1hATcFbAaD7dEY53QaxNgt4KBp
18
+ If you find this helpful, please consider a small Bitcoin donation to `1nj2kie1hATcFbAaD7dEY53QaxNgt4KBp`.
9
19
 
10
- ![Donate BTC](https://raw.github.com/wink/money-tree/master/donation_btc_qr_code.gif)
20
+ ![Donate BTC](./.github/donation_btc_qr_code.gif)
11
21
  ___
12
22
 
13
23
  ## Why would I want an HD Wallet?
@@ -60,7 +70,7 @@ These instructions assume you have a decent understanding of how Bitcoin wallets
60
70
 
61
71
  ### Create a Master Node (seed)
62
72
 
63
- To create a new HD Wallet, we're going to create a tree structure of private/public keypairs (nodes). You'll first want to start with a master node. This master node should be seeded with at least 16 random bytes but preferably 32 random bytes from a cryptographically secure PRNG (pseudo-random number generator).
73
+ To create a new HD Wallet, we're going to create a tree structure of private/public keypairs (nodes). You'll first want to start with a master node. This master node should be seeded with at least 16 random bytes but preferably 32 random bytes from a cryptographically secure PRNG (pseudo-random number generator).
64
74
 
65
75
  DO NOT use a user generated password. Keep in mind that whoever controls the seed controls ALL coins in the entire tree, so it should not be left up to a human brain, because humans tend to follow patterns and patterns are subject to brute force attacks. Luckily, MoneyTree includes the seed generation by default so you don't need to create this on your own.
66
76
 
@@ -109,19 +119,19 @@ DO NOT use a user generated password. Keep in mind that whoever controls the see
109
119
  @master.public_key.to_hex
110
120
  => "0339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2"
111
121
 
112
- @master.chain_code_hex
122
+ @master.chain_code_hex
113
123
  => "873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d508" # Look up chain codes in the BIP0032 spec
114
124
 
115
125
  @master.to_serialized_hex(:private)
116
126
  => "0488ade4000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d50800e8f32e723decf4051aefac8e2c93c9c5b214313817cdb01a1494b917c8436b35"
117
127
 
118
- @master.to_serialized_address(:private)
128
+ @master.to_bip32(:private)
119
129
  => "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
120
130
 
121
131
  @master.to_serialized_hex
122
132
  => "0488b21e000000000000000000873dff81c02f525623fd1fe5167eac3a55a049de3d314bb42ee227ffed37d5080339a36013301597daef41fbe593a02cc513d0b55527ec2df1050e2e8ff49c85c2"
123
133
 
124
- @master.to_serialized_address
134
+ @master.to_bip32
125
135
  => "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
126
136
  ```
127
137
 
@@ -140,10 +150,10 @@ To generate a child node from a given path:
140
150
  @node.depth
141
151
  => 2
142
152
 
143
- @node.to_serialized_address(:private)
153
+ @node.to_bip32(:private)
144
154
  => "xprv9ww7sMFLzJN15m7zX5JEBXQrQq8h4fU8PVqd929Hjy3xNSMzeBf163idMNBSq47DdCakyZTK7KcC2nbz3jqUkpJj8ZR4FqrijcFcFmcoBAe"
145
155
 
146
- @node.to_serialized_address
156
+ @node.to_bip32
147
157
  => "xpub6AvUGrnEpfvJJFCTd6qEYfMaxryBU8BykimDwQYuJJawFEh9BiyFdr37Cc4wEKCWWv7TsFQRUMdezXVqV9cfBUbeUEgNYCCP4omxULbNaRr"
148
158
  ```
149
159
 
@@ -163,23 +173,23 @@ Because we need multiple pieces of info to reconstruct nodes in a tree, when we'
163
173
  "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
164
174
  ```
165
175
 
166
- In addition to the key and the chain code, this encoding also includes info about the depth and index of the key, along with a fingerprint of its parent key (which I presume is for quickly sorting a big pile of keys into a tree).
167
-
176
+ In addition to the key and the chain code, this encoding also includes info about the depth and index of the key, along with a fingerprint of its parent key (which I presume is for quickly sorting a big pile of keys into a tree).
177
+
168
178
  These are the addresses that you should use to represent each node in the tree structure, however these are NOT the bitcoin addresses you should pass around for receiving money. These are more for storing inside a wallet file so that you can reconstruct the tree.
169
179
 
170
180
  To export a node to a serialized address, you can do:
171
181
 
172
182
  ```ruby
173
- @node.to_serialized_address(:private) # for private keys
183
+ @node.to_bip32(:private) # for private keys
174
184
  => "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
175
185
 
176
- @node.to_serialized_address
186
+ @node.to_bip32
177
187
  => "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
178
188
  ```
179
-
189
+
180
190
  To import from a serialized address: (either public or private)
181
191
  ```ruby
182
- @node = MoneyTree::Node.from_serialized_address "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
192
+ @node = MoneyTree::Node.from_bip32 "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
183
193
  => MoneyTree::Node instance
184
194
  ```
185
195
 
@@ -198,10 +208,10 @@ For example:
198
208
  ```ruby
199
209
  @node = @master.node_for_path("M/0/3") # or "m/0/3.pub" or "M/0/3.pub"...these are equivalent
200
210
 
201
- @node.to_serialized_address
211
+ @node.to_bip32
202
212
  => "xpub6AvUGrnEpfvJJFCTd6qEYfMaxryBU8BykimDwQYuJJawFEh9BiyFdr37Cc4wEKCWWv7TsFQRUMdezXVqV9cfBUbeUEgNYCCP4omxULbNaRr"
203
213
 
204
- @node.to_serialized_address(:private)
214
+ @node.to_bip32(:private)
205
215
  -> raises MoneyTree::Node::PrivatePublicMismatch error
206
216
  ```
207
217
 
@@ -209,13 +219,13 @@ For example:
209
219
  You can also import a node using only a public key. Keep in mind that this node will only be able to generate other public-key only nodes. You will not be able to derive child private keys using this node.
210
220
 
211
221
  ```ruby
212
- @node = MoneyTree::Node.from_serialized_address("xpub6AvUGrnEpfvJJFCTd6qEYfMaxryBU8BykimDwQYuJJawFEh9BiyFdr37Cc4wEKCWWv7TsFQRUMdezXVqV9cfBUbeUEgNYCCP4omxULbNaRr")
222
+ @node = MoneyTree::Node.from_bip32("xpub6AvUGrnEpfvJJFCTd6qEYfMaxryBU8BykimDwQYuJJawFEh9BiyFdr37Cc4wEKCWWv7TsFQRUMdezXVqV9cfBUbeUEgNYCCP4omxULbNaRr")
213
223
  => MoneyTree::Node instance
214
224
 
215
- @node.to_serialized_address
225
+ @node.to_bip32
216
226
  => "xpub6AvUGrnEpfvJJFCTd6qEYfMaxryBU8BykimDwQYuJJawFEh9BiyFdr37Cc4wEKCWWv7TsFQRUMdezXVqV9cfBUbeUEgNYCCP4omxULbNaRr"
217
227
 
218
- @node.to_serialized_address(:private)
228
+ @node.to_bip32(:private)
219
229
  -> raises MoneyTree::Node::PrivatePublicMismatch error
220
230
  ```
221
231
 
@@ -241,7 +251,7 @@ For instance:
241
251
 
242
252
  They are all equivalent ways of saying the same thing, but the first two are just a more human readable shorthand notation. You are free to use whichever notation you prefer. This gem will parse it.
243
253
 
244
- To add just a little more confusion to the mix, some wallets will use negative `i` values to also denote private derivation. Any `i` value that is negative will be processed using private derivation by this library. e.g. `"m/-1/1"`.
254
+ To add just a little more confusion to the mix, some wallets will use negative `i` values to also denote private derivation. Any `i` value that is negative will be processed using private derivation by this library. e.g. `"m/-1/1"`. (NOTE: known issue, see below)
245
255
 
246
256
 
247
257
  ## Contributing
@@ -251,3 +261,8 @@ To add just a little more confusion to the mix, some wallets will use negative `
251
261
  3. Commit your changes (`git commit -am 'Add some feature'`)
252
262
  4. Push to the branch (`git push origin my-new-feature`)
253
263
  5. Create new Pull Request
264
+
265
+ ## Known Issues (PRs welcome)
266
+
267
+ - Segwit ([BIP49](https://github.com/bitcoin/bips/blob/master/bip-0049.mediawiki)) address generation is not supported
268
+ - Use of negative integers in paths does _not_ produce the correct hardened derivation.
data/Rakefile CHANGED
@@ -3,4 +3,4 @@ require 'rspec/core/rake_task'
3
3
 
4
4
  RSpec::Core::RakeTask.new(:spec)
5
5
 
6
- task :default => :spec
6
+ task :default => :spec
@@ -0,0 +1,24 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIEHDCCAoSgAwIBAgIBATANBgkqhkiG9w0BAQsFADAcMRowGAYDVQQDDBFtYXR0
3
+ L0RDPWdlbS9EQz1jbzAeFw0xODA1MzAyMTQwNDZaFw0xOTA1MzAyMTQwNDZaMBwx
4
+ GjAYBgNVBAMMEW1hdHQvREM9Z2VtL0RDPWNvMIIBojANBgkqhkiG9w0BAQEFAAOC
5
+ AY8AMIIBigKCAYEAxfbjMHFlxA2P+4YWPagKoGAMi4078imgXdFbD3Rloe6cGfYp
6
+ IMUQitiHrKi6fhSE0UjXmoP3qnYFddm1enN9zUAFRhHWv7xpINqSqss4PYAb5Anl
7
+ RYZu3jromop5aVodi15HUfu5z27MvBm4rAaN/dDRfh/rT2hDbTTh0HmvEaPUDfX6
8
+ TyflAttfabFvtY4qsD+ao8tks0DytqyuEWZ0tvQ6upOgHRNNuYDwDZB1T9v2dq2w
9
+ 3goJFmOKBMMn7UH8WMjD3HiOuRD4tWhq5xWLjBqjzFlVPlZPgdCNyXeMMnLXER98
10
+ NY35cVWFFuqG+kZwy4MFKdE9WFTocLZxLFo0VVTNSpPara9HirbHtIo9jZNuop4S
11
+ g4JTf1F8dIWYii3sXoAYZfkl6rHVRP0G/OV5LcTfSS3QkmI5hNltz5FZzc+qI6S1
12
+ rTR1ZwTy1rRI3coFY7vDRaFWBoMbbo/DytgCE3+rfbVDxQrJa4aZ0iYDhu8LXEA1
13
+ VTtpf1EWYCOsYE1TAgMBAAGjaTBnMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgSwMB0G
14
+ A1UdDgQWBBQ6QoDNre7LFgOukH2Cv+RqZyfUzjAWBgNVHREEDzANgQttYXR0QGdl
15
+ bS5jbzAWBgNVHRIEDzANgQttYXR0QGdlbS5jbzANBgkqhkiG9w0BAQsFAAOCAYEA
16
+ kOxYnOsB+NwHwLc2lHEZ8ubxanq2qIZDhvVQ4M31gwmba43xO7vq0ktFxYRvozs4
17
+ 74dQ6bmY2e7njoFgeutyJwxulA+BC71mDQA1s4WsZo7Z2TRgB0GViVqHrzq+jY+M
18
+ p9mTHQqKH+2j0P9T4DXSzq4qOaBA3YROAwAzYI9N8MObeWkRt2pZ4zYQrAniP2nd
19
+ wzXs/G5lWbbntVcvQOfAAXBipSJ3X5P2EGpUytP9ZpGdezY5HZzuiJFcmCf1CM3t
20
+ VX4NZjbJak9gOY0AFD0Aw497sYenm0VBExclOmeRuZLffpWteTTL//utpG3bbFPl
21
+ jQ78uzsrexYTYW5IshjfSIf3TZxm50Z45pyOTow5EOP1Nd7OmKOcI8hrLGv5+AlD
22
+ hCnomUTUNsM4Rjwl5rzQiIn3ezv6+0tlg4rWJmVTuOGwcHk/oj1In2sPjCqm0pgx
23
+ TLnMa8gr6aUpuHR5s2N4ZH0Q2YIsaD6cv7DYXt+G4MRut3njOYHfkqsSVykO6hvr
24
+ -----END CERTIFICATE-----
@@ -0,0 +1 @@
1
+ ce3c7dc0fe6817aee65f990c7a97f89fd36c94380ac804c7579554d665a934df99d4e72ee9b2e467efcc76799a20c4d1de4c950bbba3512d42260c38a46e54b9
@@ -1,13 +1,13 @@
1
1
  module MoneyTree
2
2
  class Address
3
3
  attr_reader :private_key, :public_key
4
-
4
+
5
5
  def initialize(opts = {})
6
6
  private_key = opts.delete(:private_key)
7
7
  @private_key = MoneyTree::PrivateKey.new({ key: private_key }.merge(opts))
8
8
  @public_key = MoneyTree::PublicKey.new(@private_key, opts)
9
9
  end
10
-
10
+
11
11
  def to_s(network: :bitcoin)
12
12
  public_key.to_s(network: network)
13
13
  end
@@ -7,12 +7,12 @@ module MoneyTree
7
7
  include OpenSSL
8
8
  include Support
9
9
  extend Support
10
- class KeyInvalid < Exception; end
11
- class KeyGenerationFailure < Exception; end
12
- class KeyImportFailure < Exception; end
13
- class KeyFormatNotFound < Exception; end
14
- class InvalidWIFFormat < Exception; end
15
- class InvalidBase64Format < Exception; end
10
+ class KeyInvalid < StandardError; end
11
+ class KeyGenerationFailure < StandardError; end
12
+ class KeyImportFailure < StandardError; end
13
+ class KeyFormatNotFound < StandardError; end
14
+ class InvalidWIFFormat < StandardError; end
15
+ class InvalidBase64Format < StandardError; end
16
16
 
17
17
  attr_reader :options, :key, :raw_key
18
18
  attr_accessor :ec_key
@@ -70,7 +70,7 @@ module MoneyTree
70
70
  end
71
71
 
72
72
  def parse_raw_key
73
- result = if raw_key.is_a?(Bignum) then from_bignum
73
+ result = if raw_key.is_a?(Integer) then from_integer
74
74
  elsif hex_format? then from_hex
75
75
  elsif base64_format? then from_base64
76
76
  elsif compressed_wif_format? then from_wif
@@ -81,7 +81,7 @@ module MoneyTree
81
81
  result.downcase
82
82
  end
83
83
 
84
- def from_bignum(bignum = raw_key)
84
+ def from_integer(bignum = raw_key)
85
85
  # TODO: does this need a byte size specification?
86
86
  int_to_hex(bignum)
87
87
  end
@@ -213,7 +213,7 @@ module MoneyTree
213
213
  end
214
214
 
215
215
  def parse_raw_key
216
- result = if raw_key.is_a?(Bignum)
216
+ result = if raw_key.is_a?(Integer)
217
217
  set_point raw_key
218
218
  elsif hex_format?
219
219
  set_point hex_to_int(raw_key), compressed: false
@@ -5,10 +5,10 @@ module MoneyTree
5
5
  attr_reader :private_key, :public_key, :chain_code,
6
6
  :is_private, :depth, :index, :parent
7
7
 
8
- class PublicDerivationFailure < Exception; end
9
- class InvalidKeyForIndex < Exception; end
10
- class ImportError < Exception; end
11
- class PrivatePublicMismatch < Exception; end
8
+ class PublicDerivationFailure < StandardError; end
9
+ class InvalidKeyForIndex < StandardError; end
10
+ class ImportError < StandardError; end
11
+ class PrivatePublicMismatch < StandardError; end
12
12
 
13
13
  def initialize(opts = {})
14
14
  opts.each { |k, v| instance_variable_set "@#{k}", v }
@@ -90,7 +90,12 @@ module MoneyTree
90
90
  left_int = left_from_hash(hash)
91
91
  raise InvalidKeyForIndex, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
92
92
  factor = BN.new left_int.to_s
93
- child_public_key = public_key.uncompressed.group.generator.mul(factor).add(public_key.uncompressed.point).to_bn.to_i
93
+
94
+ gen_point = public_key.uncompressed.group.generator.mul(factor)
95
+
96
+ sum_point_hex = MoneyTree::OpenSSLExtensions.add(gen_point, public_key.uncompressed.point)
97
+ child_public_key = OpenSSL::PKey::EC::Point.new(public_key.group, OpenSSL::BN.new(sum_point_hex, 16)).to_bn.to_i
98
+
94
99
  raise InvalidKeyForIndex, 'at infinity' if child_public_key == 1/0.0 # very low probability
95
100
  child_chain_code = right_from_hash(hash)
96
101
  return child_public_key, child_chain_code
@@ -4,11 +4,11 @@ require 'base64'
4
4
  module MoneyTree
5
5
  module Support
6
6
  include OpenSSL
7
-
7
+
8
8
  INT32_MAX = 256 ** [1].pack("L*").size
9
9
  INT64_MAX = 256 ** [1].pack("Q*").size
10
10
  BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
11
-
11
+
12
12
  def int_to_base58(int_val, leading_zero_bytes=0)
13
13
  base58_val, base = '', BASE58_CHARS.size
14
14
  while int_val > 0
@@ -40,7 +40,7 @@ module MoneyTree
40
40
  s
41
41
  end
42
42
  alias_method :base58_to_hex, :decode_base58
43
-
43
+
44
44
  def to_serialized_base58(hex)
45
45
  hash = sha256 hex
46
46
  hash = sha256 hash
@@ -48,7 +48,7 @@ module MoneyTree
48
48
  address = hex + checksum
49
49
  encode_base58 address
50
50
  end
51
-
51
+
52
52
  def from_serialized_base58(base58)
53
53
  hex = decode_base58 base58
54
54
  checksum = hex.slice!(-8..-1)
@@ -56,45 +56,45 @@ module MoneyTree
56
56
  raise EncodingError unless checksum == compare_checksum
57
57
  hex
58
58
  end
59
-
59
+
60
60
  def digestify(digest_type, source, opts = {})
61
61
  source = [source].pack("H*") unless opts[:ascii]
62
62
  bytes_to_hex Digest.digest(digest_type, source)
63
63
  end
64
-
64
+
65
65
  def sha256(source, opts = {})
66
66
  digestify('SHA256', source, opts)
67
67
  end
68
-
68
+
69
69
  def ripemd160(source, opts = {})
70
70
  digestify('RIPEMD160', source, opts)
71
71
  end
72
-
72
+
73
73
  def encode_base64(hex)
74
74
  Base64.encode64([hex].pack("H*")).chomp
75
75
  end
76
-
76
+
77
77
  def decode_base64(base64)
78
78
  Base64.decode64(base64).unpack("H*")[0]
79
79
  end
80
-
80
+
81
81
  def hmac_sha512(key, message)
82
82
  digest = Digest::SHA512.new
83
83
  HMAC.digest digest, key, message
84
84
  end
85
-
85
+
86
86
  def hmac_sha512_hex(key, message)
87
87
  md = hmac_sha512(key, message)
88
88
  md.unpack("H*").first.rjust(64, '0')
89
89
  end
90
-
90
+
91
91
  def bytes_to_int(bytes, base = 16)
92
92
  if bytes.is_a?(Array)
93
93
  bytes = bytes.pack("C*")
94
94
  end
95
95
  bytes.unpack("H*")[0].to_i(16)
96
96
  end
97
-
97
+
98
98
  def int_to_hex(i, size=nil)
99
99
  hex = i.to_s(16).downcase
100
100
  if (hex.size % 2) != 0
@@ -107,19 +107,19 @@ module MoneyTree
107
107
  hex
108
108
  end
109
109
  end
110
-
110
+
111
111
  def int_to_bytes(i)
112
112
  [int_to_hex(i)].pack("H*")
113
113
  end
114
-
114
+
115
115
  def bytes_to_hex(bytes)
116
116
  bytes.unpack("H*")[0].downcase
117
117
  end
118
-
118
+
119
119
  def hex_to_bytes(hex)
120
120
  [hex].pack("H*")
121
121
  end
122
-
122
+
123
123
  def hex_to_int(hex)
124
124
  hex.to_i(16)
125
125
  end
@@ -1,3 +1,3 @@
1
1
  module MoneyTree
2
- VERSION = "0.9.0"
2
+ VERSION = "0.11.0"
3
3
  end
data/lib/money-tree.rb CHANGED
@@ -8,5 +8,4 @@ require "money-tree/networks"
8
8
  require "money-tree/node"
9
9
 
10
10
  module MoneyTree
11
-
12
11
  end
@@ -1,73 +1,28 @@
1
1
  # encoding: ascii-8bit
2
2
 
3
3
  require 'openssl'
4
- require 'ffi'
5
4
 
6
5
  module MoneyTree
7
6
  module OpenSSLExtensions
8
- extend FFI::Library
9
- ffi_lib 'ssl'
10
-
11
- NID_secp256k1 = 714
12
- POINT_CONVERSION_COMPRESSED = 2
13
- POINT_CONVERSION_UNCOMPRESSED = 4
14
-
15
- attach_function :EC_KEY_free, [:pointer], :int
16
- attach_function :EC_KEY_get0_group, [:pointer], :pointer
17
- attach_function :EC_KEY_new_by_curve_name, [:int], :pointer
18
- attach_function :EC_POINT_clear_free, [:pointer], :int
19
- attach_function :EC_POINT_add, [:pointer, :pointer, :pointer, :pointer, :pointer], :int
20
- attach_function :EC_POINT_point2hex, [:pointer, :pointer, :int, :pointer], :string
21
- attach_function :EC_POINT_hex2point, [:pointer, :string, :pointer, :pointer], :pointer
22
- attach_function :EC_POINT_new, [:pointer], :pointer
23
-
24
7
  def self.add(point_0, point_1)
25
8
  validate_points(point_0, point_1)
26
- eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
27
- group = EC_KEY_get0_group(eckey)
28
-
9
+ group = OpenSSL::PKey::EC::Group.new('secp256k1')
29
10
  point_0_hex = point_0.to_bn.to_s(16)
30
- point_0_pt = EC_POINT_hex2point(group, point_0_hex, nil, nil)
11
+ point_0_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_0_hex, 16))
31
12
  point_1_hex = point_1.to_bn.to_s(16)
32
- point_1_pt = EC_POINT_hex2point(group, point_1_hex, nil, nil)
33
-
34
- sum_point = EC_POINT_new(group)
35
- success = EC_POINT_add(group, sum_point, point_0_pt, point_1_pt, nil)
36
- hex = EC_POINT_point2hex(group, sum_point, POINT_CONVERSION_UNCOMPRESSED, nil)
37
-
38
- EC_KEY_free(eckey)
39
- EC_POINT_clear_free(sum_point)
40
- EC_POINT_clear_free(point_0_pt)
41
- EC_POINT_clear_free(point_1_pt)
42
-
43
- eckey = nil
44
- group = nil
45
- sum_point = nil
46
- point_0_pt = nil
47
- point_1_pt = nil
48
-
49
- hex
13
+ point_1_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_1_hex, 16))
14
+ sum_point = point_0_pt.add(point_1_pt)
15
+ sum_point.to_bn.to_s(16)
50
16
  end
51
17
 
52
18
  def self.validate_points(*points)
53
19
  points.each do |point|
54
20
  if !point.is_a?(OpenSSL::PKey::EC::Point)
55
- raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
21
+ raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
56
22
  elsif point.infinity?
57
- raise ArgumentError, "point must not be infinity"
23
+ raise ArgumentError, "point must not be infinity"
58
24
  end
59
25
  end
60
26
  end
61
27
  end
62
28
  end
63
-
64
-
65
- class OpenSSL::PKey::EC::Point
66
- include MoneyTree::OpenSSLExtensions
67
-
68
- def add(point)
69
- sum_point_hex = MoneyTree::OpenSSLExtensions.add(self, point)
70
- self.class.new group, OpenSSL::BN.new(sum_point_hex, 16)
71
- end
72
-
73
- end
data/money-tree.gemspec CHANGED
@@ -6,33 +6,22 @@ require 'money-tree/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = "money-tree"
8
8
  spec.version = MoneyTree::VERSION
9
- spec.authors = ["Micah Winkelspecht"]
10
- spec.email = ["winkelspecht@gmail.com"]
9
+ spec.authors = ["Micah Winkelspecht", "Afri Schoedon"]
10
+ spec.email = ["winkelspecht@gmail.com", "gems@q9f.cc"]
11
11
  spec.description = %q{A Ruby Gem implementation of Bitcoin HD Wallets}
12
12
  spec.summary = %q{Bitcoin Hierarchical Deterministic Wallets in Ruby! (Bitcoin standard BIP0032)}
13
- spec.homepage = ""
13
+ spec.homepage = "https://github.com/gemhq/money-tree"
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
17
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
-
21
- # used with gem i coin-op -P HighSecurity
22
- spec.cert_chain = ["certs/jvergeldedios.pem"]
23
- # Sign gem when evaluating spec with `gem` command
24
- # unless ENV has set a SKIP_GEM_SIGNING
25
- if ($0 =~ /gem\z/) and not ENV.include?("SKIP_GEM_SIGNING")
26
- spec.signing_key = File.join(Gem.user_home, ".ssh", "gem-private_key.pem")
27
- end
28
20
 
29
-
30
- spec.add_dependency "ffi"
31
-
32
- spec.add_development_dependency "bundler", "~> 1.3"
33
- spec.add_development_dependency "rake"
34
- spec.add_development_dependency "rspec"
35
- spec.add_development_dependency "simplecov"
36
- spec.add_development_dependency "coveralls"
37
- spec.add_development_dependency "pry"
21
+ spec.add_dependency 'openssl', '>= 2.2'
22
+
23
+ spec.add_development_dependency "bundler", "~> 2.2"
24
+ spec.add_development_dependency "rake", "~> 13.0"
25
+ spec.add_development_dependency "rspec", "~> 3.10"
26
+ spec.add_development_dependency "pry", "~> 0.4"
38
27
  end
@@ -4,14 +4,20 @@ describe MoneyTree::Address do
4
4
  describe "initialize" do
5
5
  it "generates a private key by default" do
6
6
  address = MoneyTree::Address.new
7
+ expect(address).to be
8
+ expect(address).to be_instance_of MoneyTree::Address
9
+ expect(address.private_key).to be_instance_of MoneyTree::PrivateKey
7
10
  expect(address.private_key.key.length).to eql(64)
8
11
  end
9
-
12
+
10
13
  it "generates a public key by default" do
11
14
  address = MoneyTree::Address.new
15
+ expect(address).to be
16
+ expect(address).to be_instance_of MoneyTree::Address
17
+ expect(address.public_key).to be_instance_of MoneyTree::PublicKey
12
18
  expect(address.public_key.key.length).to eql(66)
13
19
  end
14
-
20
+
15
21
  it "imports a private key in hex form" do
16
22
  address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
17
23
  expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
@@ -20,36 +26,46 @@ describe MoneyTree::Address do
20
26
  expect(address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
21
27
  expect(address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
22
28
  end
23
-
29
+
24
30
  it "imports a private key in compressed wif format" do
25
31
  address = MoneyTree::Address.new private_key: "KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK"
26
32
  expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
27
33
  expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
28
34
  expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
29
35
  end
30
-
36
+
31
37
  it "imports a private key in uncompressed wif format" do
32
38
  address = MoneyTree::Address.new private_key: "5JXz5ZyFk31oHVTQxqce7yitCmTAPxBqeGQ4b7H3Aj3L45wUhoa"
33
39
  expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
34
40
  expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
35
41
  end
36
42
  end
37
-
43
+
38
44
  describe "to_s" do
39
45
  before do
40
46
  @address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
41
47
  end
42
-
48
+
43
49
  it "returns compressed base58 public key" do
44
50
  expect(@address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
45
51
  expect(@address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
46
52
  end
47
-
53
+
48
54
  it "returns compressed WIF private key" do
49
55
  expect(@address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
50
56
  end
51
57
  end
52
58
 
59
+ context "bitcoin wiki" do
60
+ # ref https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses
61
+ subject(:wiki) { MoneyTree::Address.new private_key: '18e14a7b6a307f426a94f8114701e7c8e774e7f9a47e2c2035db29a206321725' }
62
+
63
+ it "always regenerates the bitcoin wiki example" do
64
+ expect(wiki.public_key.key).to eq "0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352"
65
+ expect(wiki.to_s).to eq "1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs"
66
+ end
67
+ end
68
+
53
69
  context "testnet3" do
54
70
  before do
55
71
  @address = MoneyTree::Address.new network: :bitcoin_testnet