money-tree 0.8.4 → 0.8.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e6a3ecbdcd5b3a5f520f5d34eedb0158ad7ad716
4
- data.tar.gz: 999bc086f43b012af5b2de444c43bac102e2cbe1
3
+ metadata.gz: ed8522c0f961d7b6556cc9067455f12d65bcfd0f
4
+ data.tar.gz: 02857ab85d9b22a1a96c71f6d47c957a9310eb23
5
5
  SHA512:
6
- metadata.gz: 10019f4bf74c84af796336033c0f67ae2fac8df851c028e95942a1c717a9afe78be6ae190c9d8de932a16e461e950645c9d0023e9a2e084a8e54a09fd1df6e3d
7
- data.tar.gz: 70d2850af773d64babf1ce5711e055f79bdfa281fc1b92d7a07bdecad4bed3022977e06d079eed64573e2ecc1e633b429af7b7063cc1c552ea9711459af61f0b
6
+ metadata.gz: b9d8647c31dccee3b9c34ad46e533bbd0cd37c8407402caa1356e8282b3e3e422637b69ebbee42a840c64948ccca8e9d5479fb0a260fa7d8508790210f9317ae
7
+ data.tar.gz: 22fbe674fbb0919ced227c8d3bd5f9f554b76726fca29442b2dd545c810f05e9dd5b97fa1f2f2ceaedaeb06e8b58f7b2c6bcb8f8e6d57e4d1103cc3c250369ac
@@ -1,10 +1,11 @@
1
1
  module MoneyTree
2
2
  class Address
3
- attr_accessor :private_key, :public_key
3
+ attr_reader :private_key, :public_key
4
4
 
5
5
  def initialize(opts = {})
6
- @private_key = MoneyTree::PrivateKey.new key: opts[:private_key]
7
- @public_key = MoneyTree::PublicKey.new(@private_key)
6
+ private_key = opts.delete(:private_key)
7
+ @private_key = MoneyTree::PrivateKey.new({ key: private_key }.merge(opts))
8
+ @public_key = MoneyTree::PublicKey.new(@private_key, opts)
8
9
  end
9
10
 
10
11
  def to_s
@@ -15,7 +15,7 @@ module MoneyTree
15
15
  class InvalidWIFFormat < Exception; end
16
16
  class InvalidBase64Format < Exception; end
17
17
 
18
- attr_reader :options, :key, :raw_key
18
+ attr_reader :options, :key, :raw_key, :network, :network_key
19
19
  attr_accessor :ec_key
20
20
 
21
21
  GROUP_NAME = 'secp256k1'
@@ -40,6 +40,8 @@ module MoneyTree
40
40
  def initialize(opts = {})
41
41
  @options = opts
42
42
  @ec_key = PKey::EC.new GROUP_NAME
43
+ @network_key = options[:network] || :bitcoin
44
+ @network = MoneyTree::NETWORKS[network_key]
43
45
  if @options[:key]
44
46
  @raw_key = @options[:key]
45
47
  @key = parse_raw_key
@@ -71,8 +73,8 @@ module MoneyTree
71
73
  end
72
74
 
73
75
  def parse_raw_key
74
- result = if raw_key.is_a?(Bignum) then int_to_hex(raw_key)
75
- elsif hex_format? then raw_key
76
+ result = if raw_key.is_a?(Bignum) then from_bignum
77
+ elsif hex_format? then from_hex
76
78
  elsif base64_format? then from_base64
77
79
  elsif compressed_wif_format? then from_wif
78
80
  elsif uncompressed_wif_format? then from_wif
@@ -81,28 +83,52 @@ module MoneyTree
81
83
  end
82
84
  result.downcase
83
85
  end
86
+
87
+ def from_bignum(bignum = raw_key)
88
+ int_to_hex(bignum)
89
+ end
90
+
91
+ def from_hex(hex = raw_key)
92
+ hex
93
+ end
84
94
 
85
95
  def from_wif(wif = raw_key)
86
96
  compressed = wif.length == 52
97
+ parse_network_from_wif(wif, compressed: compressed)
87
98
  validate_wif(wif)
88
99
  hex = decode_base58(wif)
89
100
  last_char = compressed ? -11 : -9
90
101
  hex.slice(2..last_char)
91
102
  end
103
+
104
+ def parse_network_from_wif(wif, opts = {})
105
+ networks = MoneyTree::NETWORKS
106
+ chars_key = opts[:compressed] ? :compressed_wif_chars : :uncompressed_wif_chars
107
+ @network_key = networks.keys.select do |k|
108
+ networks[k][chars_key].include?(wif.slice(0))
109
+ end.first
110
+ @network = networks[network_key]
111
+ end
92
112
 
93
113
  def from_base64(base64_key = raw_key)
94
114
  raise InvalidBase64Format unless base64_format?(base64_key)
95
115
  decode_base64(base64_key)
96
116
  end
97
-
117
+
98
118
  def compressed_wif_format?
99
- raw_key.length == 52 && MoneyTree::NETWORKS[:bitcoin][:compressed_wif_chars].include?(raw_key.slice(0))
119
+ wif_format?(:compressed)
100
120
  end
101
121
 
102
122
  def uncompressed_wif_format?
103
- raw_key.length == 51 && raw_key.slice(0) == MoneyTree::NETWORKS[:bitcoin][:uncompressed_wif_char]
123
+ wif_format?(:uncompressed)
104
124
  end
105
-
125
+
126
+ def wif_format?(compression)
127
+ length = compression == :compressed ? 52 : 51
128
+ wif_prefixes = MoneyTree::NETWORKS.map {|k, v| v["#{compression}_wif_chars".to_sym]}.flatten
129
+ raw_key.length == length && wif_prefixes.include?(raw_key.slice(0))
130
+ end
131
+
106
132
  def base64_format?(base64_key = raw_key)
107
133
  base64_key.length == 44 && base64_key =~ /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/
108
134
  end
@@ -117,8 +143,8 @@ module MoneyTree
117
143
 
118
144
  def to_wif(opts = {})
119
145
  opts[:compressed] = true unless opts[:compressed] == false
120
- source = MoneyTree::NETWORKS[:bitcoin][:privkey_version] + to_hex
121
- source += MoneyTree::NETWORKS[:bitcoin][:privkey_compression_flag] if opts[:compressed]
146
+ source = network[:privkey_version] + to_hex
147
+ source += network[:privkey_compression_flag] if opts[:compressed]
122
148
  hash = sha256(source)
123
149
  hash = sha256(hash)
124
150
  checksum = hash.slice(0..7)
@@ -128,7 +154,7 @@ module MoneyTree
128
154
 
129
155
  def wif_valid?(wif)
130
156
  hex = decode_base58(wif)
131
- return false unless hex.slice(0..1) == MoneyTree::NETWORKS[:bitcoin][:privkey_version]
157
+ return false unless hex.slice(0..1) == network[:privkey_version]
132
158
  checksum = hex.chars.to_a.pop(8).join
133
159
  source = hex.slice(0..-9)
134
160
  hash = sha256(source)
@@ -160,10 +186,14 @@ module MoneyTree
160
186
 
161
187
  if p_key.is_a?(PrivateKey)
162
188
  @private_key = p_key
189
+ @network_key = private_key.network_key
190
+ @network = MoneyTree::NETWORKS[network_key]
163
191
  @point = @private_key.calculate_public_key(@options)
164
192
  @group = @point.group
165
193
  @key = @raw_key = to_hex
166
194
  else
195
+ @network_key = @options[:network] || :bitcoin
196
+ @network = MoneyTree::NETWORKS[network_key]
167
197
  @raw_key = p_key
168
198
  @group = PKey::EC::Group.new GROUP_NAME
169
199
  @key = parse_raw_key
@@ -180,13 +210,13 @@ module MoneyTree
180
210
  end
181
211
 
182
212
  def compressed
183
- compressed_key = self.class.new raw_key # deep clone
213
+ compressed_key = self.class.new raw_key, options # deep clone
184
214
  compressed_key.set_point to_i, compressed: true
185
215
  compressed_key
186
216
  end
187
217
 
188
218
  def uncompressed
189
- uncompressed_key = self.class.new raw_key # deep clone
219
+ uncompressed_key = self.class.new raw_key, options # deep clone
190
220
  uncompressed_key.set_point to_i, compressed: false
191
221
  uncompressed_key
192
222
  end
@@ -236,7 +266,7 @@ module MoneyTree
236
266
 
237
267
  def to_address
238
268
  hash = to_ripemd160
239
- address = MoneyTree::NETWORKS[:bitcoin][:address_version] + hash
269
+ address = network[:address_version] + hash
240
270
  to_serialized_base58 address
241
271
  end
242
272
  alias :to_s :to_address
@@ -3,24 +3,26 @@ module MoneyTree
3
3
  bitcoin: {
4
4
  address_version: '00',
5
5
  p2sh_version: '05',
6
+ p2sh_char: '3',
6
7
  privkey_version: '80',
7
8
  privkey_compression_flag: '01',
8
9
  extended_privkey_version: "0488ade4",
9
10
  extended_pubkey_version: "0488b21e",
10
11
  compressed_wif_chars: %w(K L),
11
- uncompressed_wif_char: '5',
12
+ uncompressed_wif_chars: %w(5),
12
13
  protocol_version: 70001
13
14
  },
14
15
  bitcoin_testnet: {
15
16
  address_version: '6f',
16
- p2sh_version: '05',
17
- privkey_version: '80',
17
+ p2sh_version: 'c4',
18
+ p2sh_char: '2',
19
+ privkey_version: 'ef',
18
20
  privkey_compression_flag: '01',
19
21
  extended_privkey_version: "04358394",
20
22
  extended_pubkey_version: "043587cf",
21
- compressed_wif_chars: %w(K L),
22
- uncompressed_wif_char: '5',
23
+ compressed_wif_chars: %w(c),
24
+ uncompressed_wif_chars: %w(9),
23
25
  protocol_version: 70001
24
26
  }
25
27
  }
26
- end
28
+ end
@@ -2,7 +2,7 @@ module MoneyTree
2
2
  class Node
3
3
  include Support
4
4
  extend Support
5
- attr_reader :private_key, :public_key, :chain_code, :is_private, :depth, :index, :parent, :is_test
5
+ attr_reader :private_key, :public_key, :chain_code, :is_private, :depth, :index, :parent, :network, :network_key
6
6
 
7
7
  class PublicDerivationFailure < Exception; end
8
8
  class InvalidKeyForIndex < Exception; end
@@ -10,6 +10,8 @@ module MoneyTree
10
10
  class PrivatePublicMismatch < Exception; end
11
11
 
12
12
  def initialize(opts = {})
13
+ @network_key = opts.delete(:network) || :bitcoin
14
+ @network = MoneyTree::NETWORKS[network_key]
13
15
  opts.each { |k, v| instance_variable_set "@#{k}", v }
14
16
  end
15
17
 
@@ -17,7 +19,6 @@ module MoneyTree
17
19
  hex = from_serialized_base58 address
18
20
  version = from_version_hex hex.slice!(0..7)
19
21
  self.new({
20
- is_test: version[:test],
21
22
  depth: hex.slice!(0..1).to_i(16),
22
23
  fingerprint: hex.slice!(0..7),
23
24
  index: hex.slice!(0..7).to_i(16),
@@ -26,11 +27,12 @@ module MoneyTree
26
27
  end
27
28
 
28
29
  def self.key_options(hex, version)
30
+ k_opts = { network: version[:network] }
29
31
  if version[:private_key] && hex.slice(0..1) == '00'
30
- private_key = MoneyTree::PrivateKey.new key: hex.slice(2..-1)
31
- { private_key: private_key, public_key: MoneyTree::PublicKey.new(private_key) }
32
+ private_key = MoneyTree::PrivateKey.new({ key: hex.slice(2..-1) }.merge(k_opts))
33
+ k_opts.merge private_key: private_key, public_key: MoneyTree::PublicKey.new(private_key)
32
34
  elsif %w(02 03).include? hex.slice(0..1)
33
- { public_key: MoneyTree::PublicKey.new(hex) }
35
+ k_opts.merge public_key: MoneyTree::PublicKey.new(hex, k_opts)
34
36
  else
35
37
  raise ImportError, 'Public or private key data does not match version type'
36
38
  end
@@ -39,13 +41,13 @@ module MoneyTree
39
41
  def self.from_version_hex(hex)
40
42
  case hex
41
43
  when MoneyTree::NETWORKS[:bitcoin][:extended_privkey_version]
42
- { private_key: true, test: false }
44
+ { private_key: true, network: :bitcoin }
43
45
  when MoneyTree::NETWORKS[:bitcoin][:extended_pubkey_version]
44
- { private_key: false, test: false }
46
+ { private_key: false, network: :bitcoin }
45
47
  when MoneyTree::NETWORKS[:bitcoin_testnet][:extended_privkey_version]
46
- { private_key: true, test: true }
48
+ { private_key: true, network: :bitcoin_testnet }
47
49
  when MoneyTree::NETWORKS[:bitcoin_testnet][:extended_pubkey_version]
48
- { private_key: false, test: true }
50
+ { private_key: false, network: :bitcoin_testnet }
49
51
  else
50
52
  raise ImportError, 'invalid version bytes'
51
53
  end
@@ -114,7 +116,7 @@ module MoneyTree
114
116
  def to_serialized_hex(type = :public)
115
117
  raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
116
118
  version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
117
- hex = MoneyTree::NETWORKS[:bitcoin][version_key] # version (4 bytes)
119
+ hex = network[version_key] # version (4 bytes)
118
120
  hex += depth_hex(depth) # depth (1 byte)
119
121
  hex += depth.zero? ? '00000000' : parent.to_fingerprint# fingerprint of key (4 bytes)
120
122
  hex += index_hex(index) # child number i (4 bytes)
@@ -136,21 +138,22 @@ module MoneyTree
136
138
  end
137
139
 
138
140
  def to_address
139
- address = MoneyTree::NETWORKS[:bitcoin][:address_version] + to_identifier
141
+ address = network[:address_version] + to_identifier
140
142
  to_serialized_base58 address
141
143
  end
142
144
 
143
145
  def subnode(i = 0, opts = {})
144
146
  if private_key.nil?
145
147
  child_public_key, child_chain_code = derive_public_key(i)
146
- child_public_key = MoneyTree::PublicKey.new child_public_key
148
+ child_public_key = MoneyTree::PublicKey.new child_public_key, network: network_key
147
149
  else
148
150
  child_private_key, child_chain_code = derive_private_key(i)
149
- child_private_key = MoneyTree::PrivateKey.new key: child_private_key
151
+ child_private_key = MoneyTree::PrivateKey.new key: child_private_key, network: network_key
150
152
  child_public_key = MoneyTree::PublicKey.new child_private_key
151
153
  end
152
154
 
153
- MoneyTree::Node.new depth: depth+1,
155
+ MoneyTree::Node.new network: network_key,
156
+ depth: depth+1,
154
157
  index: i,
155
158
  private_key: private_key.nil? ? nil : child_private_key,
156
159
  public_key: child_public_key,
@@ -233,6 +236,8 @@ module MoneyTree
233
236
  @depth = 0
234
237
  @index = 0
235
238
  opts[:seed] = [opts[:seed_hex]].pack("H*") if opts[:seed_hex]
239
+ @network_key = opts[:network] || :bitcoin
240
+ @network = MoneyTree::NETWORKS[network_key]
236
241
  if opts[:seed]
237
242
  @seed = opts[:seed]
238
243
  @seed_hash = generate_seed_hash(@seed)
@@ -243,9 +248,15 @@ module MoneyTree
243
248
  @chain_code = opts[:chain_code]
244
249
  if opts[:private_key]
245
250
  @private_key = opts[:private_key]
251
+ @network_key = @private_key.network_key
252
+ @network = MoneyTree::NETWORKS[network_key]
246
253
  @public_key = MoneyTree::PublicKey.new @private_key
247
254
  else opts[:public_key]
248
- @public_key = opts[:public_key].is_a?(MoneyTree::PublicKey) ? opts[:public_key] : MoneyTree::PublicKey.new(opts[:public_key])
255
+ @public_key = if opts[:public_key].is_a?(MoneyTree::PublicKey)
256
+ opts[:public_key]
257
+ else
258
+ MoneyTree::PublicKey.new(opts[:public_key], network: network_key)
259
+ end
249
260
  end
250
261
  else
251
262
  generate_seed
@@ -274,7 +285,7 @@ module MoneyTree
274
285
  end
275
286
 
276
287
  def set_seeded_keys
277
- @private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash)
288
+ @private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash), network: network_key
278
289
  @chain_code = right_from_hash(seed_hash)
279
290
  @public_key = MoneyTree::PublicKey.new @private_key
280
291
  end
@@ -1,3 +1,3 @@
1
1
  module MoneyTree
2
- VERSION = "0.8.4"
2
+ VERSION = "0.8.5"
3
3
  end
@@ -49,4 +49,14 @@ describe MoneyTree::Address do
49
49
  @address.private_key.to_s.should == "KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK"
50
50
  end
51
51
  end
52
+
53
+ context "testnet3" do
54
+ before do
55
+ @address = MoneyTree::Address.new network: :bitcoin_testnet
56
+ end
57
+
58
+ it "returns a testnet address" do
59
+ %w(m n).should include(@address.to_s[0])
60
+ end
61
+ end
52
62
  end
@@ -12,7 +12,59 @@ describe MoneyTree::Master do
12
12
  @master.seed.bytesize.should == 32
13
13
  end
14
14
  end
15
-
15
+
16
+ context "testnet" do
17
+ before do
18
+ @master = MoneyTree::Master.new network: :bitcoin_testnet
19
+ end
20
+
21
+ it "generates testnet address" do
22
+ %w(m n).should include(@master.to_address[0])
23
+ end
24
+
25
+ it "generates testnet compressed wif" do
26
+ @master.private_key.to_wif[0].should == 'c'
27
+ end
28
+
29
+ it "generates testnet uncompressed wif" do
30
+ @master.private_key.to_wif(compressed: false)[0].should == '9'
31
+ end
32
+
33
+ it "generates testnet serialized private address" do
34
+ @master.to_serialized_address(:private).slice(0, 4).should == "tprv"
35
+ end
36
+
37
+ it "generates testnet serialized public address" do
38
+ @master.to_serialized_address.slice(0, 4).should == "tpub"
39
+ end
40
+
41
+ it "imports from testnet serialized private address" do
42
+ node = MoneyTree::Node.from_serialized_address 'tprv8ZgxMBicQKsPcuN7bfUZqq78UEYapr3Tzmc9NcDXw8BnBJ47dZYr6SusnfYj7vbAYP9CP8ZiD5aVBTUo1yU5QP56mepKVvuEbu8KZQXMKNE'
43
+ node.to_serialized_address(:private).should == 'tprv8ZgxMBicQKsPcuN7bfUZqq78UEYapr3Tzmc9NcDXw8BnBJ47dZYr6SusnfYj7vbAYP9CP8ZiD5aVBTUo1yU5QP56mepKVvuEbu8KZQXMKNE'
44
+ end
45
+
46
+ it "imports from testnet serialized public address" do
47
+ node = MoneyTree::Node.from_serialized_address 'tpubD6NzVbkrYhZ4YA8aUE9bBZTSyHJibBqwDny5urfwDdJc4W8od3y3Ebzy6CqsYn9CCC5P5VQ7CeZYpnT1kX3RPVPysU2rFRvYSj8BCoYYNqT'
48
+ %w(m n).should include(node.public_key.to_s[0])
49
+ node.to_serialized_address.should == 'tpubD6NzVbkrYhZ4YA8aUE9bBZTSyHJibBqwDny5urfwDdJc4W8od3y3Ebzy6CqsYn9CCC5P5VQ7CeZYpnT1kX3RPVPysU2rFRvYSj8BCoYYNqT'
50
+ end
51
+
52
+ it "generates testnet subnodes from serialized private address" do
53
+ node = MoneyTree::Node.from_serialized_address 'tprv8ZgxMBicQKsPcuN7bfUZqq78UEYapr3Tzmc9NcDXw8BnBJ47dZYr6SusnfYj7vbAYP9CP8ZiD5aVBTUo1yU5QP56mepKVvuEbu8KZQXMKNE'
54
+ subnode = node.node_for_path('1/1/1')
55
+ %w(m n).should include(subnode.public_key.to_s[0])
56
+ subnode.to_serialized_address(:private).slice(0,4).should == 'tprv'
57
+ subnode.to_serialized_address.slice(0,4).should == 'tpub'
58
+ end
59
+
60
+ it "generates testnet subnodes from serialized public address" do
61
+ node = MoneyTree::Node.from_serialized_address 'tpubD6NzVbkrYhZ4YA8aUE9bBZTSyHJibBqwDny5urfwDdJc4W8od3y3Ebzy6CqsYn9CCC5P5VQ7CeZYpnT1kX3RPVPysU2rFRvYSj8BCoYYNqT'
62
+ subnode = node.node_for_path('1/1/1')
63
+ %w(m n).should include(subnode.public_key.to_s[0])
64
+ subnode.to_serialized_address.slice(0,4).should == 'tpub'
65
+ end
66
+ end
67
+
16
68
  describe "Test vector 1" do
17
69
  describe "from a seed" do
18
70
  before do
@@ -91,4 +91,16 @@ describe MoneyTree::PrivateKey do
91
91
 
92
92
  end
93
93
  end
94
+
95
+ context "testnet" do
96
+ before do
97
+ @key = MoneyTree::PrivateKey.new key: 'cRhes8SBnsF6WizphaRKQKZZfDniDa9Bxcw31yKeEC1KDExhxFgD'
98
+ end
99
+
100
+ describe "to_wif" do
101
+ it "returns same wif" do
102
+ @key.to_wif.should == 'cRhes8SBnsF6WizphaRKQKZZfDniDa9Bxcw31yKeEC1KDExhxFgD'
103
+ end
104
+ end
105
+ end
94
106
  end
@@ -157,4 +157,27 @@ describe MoneyTree::PublicKey do
157
157
  before_str.should == after_str
158
158
  end
159
159
  end
160
+
161
+ context "testnet" do
162
+ context 'with private key' do
163
+ before do
164
+ @private_key = MoneyTree::PrivateKey.new network: :bitcoin_testnet
165
+ @key = MoneyTree::PublicKey.new(@private_key)
166
+ end
167
+
168
+ it "should have an address starting with m or n" do
169
+ %w(m n).should include(@key.to_s[0])
170
+ end
171
+ end
172
+
173
+ context 'without private key' do
174
+ before do
175
+ @key = MoneyTree::PublicKey.new('0297b033ba894611345a0e777861237ef1632370fbd58ebe644eb9f3714e8fe2bc', network: :bitcoin_testnet)
176
+ end
177
+
178
+ it "should have an address starting with m or n" do
179
+ %w(m n).should include(@key.to_s[0])
180
+ end
181
+ end
182
+ end
160
183
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: money-tree
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.4
4
+ version: 0.8.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Micah Winkelspecht
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-12-19 00:00:00.000000000 Z
11
+ date: 2013-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: ffi