money-tree 0.8.4 → 0.8.5

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
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