money-tree-openssl 1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,35 @@
1
+ module MoneyTree
2
+ NETWORKS =
3
+ begin
4
+ hsh = Hash.new do |_, key|
5
+ raise "#{key} is not a valid network!"
6
+ end.merge(
7
+ bitcoin: {
8
+ address_version: '00',
9
+ p2sh_version: '05',
10
+ p2sh_char: '3',
11
+ privkey_version: '80',
12
+ privkey_compression_flag: '01',
13
+ extended_privkey_version: "0488ade4",
14
+ extended_pubkey_version: "0488b21e",
15
+ compressed_wif_chars: %w(K L),
16
+ uncompressed_wif_chars: %w(5),
17
+ protocol_version: 70001
18
+ },
19
+ bitcoin_testnet: {
20
+ address_version: '6f',
21
+ p2sh_version: 'c4',
22
+ p2sh_char: '2',
23
+ privkey_version: 'ef',
24
+ privkey_compression_flag: '01',
25
+ extended_privkey_version: "04358394",
26
+ extended_pubkey_version: "043587cf",
27
+ compressed_wif_chars: %w(c),
28
+ uncompressed_wif_chars: %w(9),
29
+ protocol_version: 70001
30
+ }
31
+ )
32
+ hsh[:testnet3] = hsh[:bitcoin_testnet]
33
+ hsh
34
+ end
35
+ end
@@ -0,0 +1,302 @@
1
+ module MoneyTree
2
+ class Node
3
+ include Support
4
+ extend Support
5
+ attr_reader :private_key, :public_key, :chain_code,
6
+ :is_private, :depth, :index, :parent
7
+
8
+ class PublicDerivationFailure < StandardError; end
9
+ class InvalidKeyForIndex < StandardError; end
10
+ class ImportError < StandardError; end
11
+ class PrivatePublicMismatch < StandardError; end
12
+
13
+ def initialize(opts = {})
14
+ opts.each { |k, v| instance_variable_set "@#{k}", v }
15
+ end
16
+
17
+ def self.from_bip32(address, has_version: true)
18
+ hex = from_serialized_base58 address
19
+ hex.slice!(0..7) if has_version
20
+ self.new({
21
+ depth: hex.slice!(0..1).to_i(16),
22
+ parent_fingerprint: hex.slice!(0..7),
23
+ index: hex.slice!(0..7).to_i(16),
24
+ chain_code: hex.slice!(0..63).to_i(16)
25
+ }.merge(parse_out_key(hex)))
26
+ end
27
+
28
+ def self.from_serialized_address(address)
29
+ puts 'Node.from_serialized_address is DEPRECATED. Please use .from_bip32 instead.'
30
+ from_bip32(address)
31
+ end
32
+
33
+ def self.parse_out_key(hex)
34
+ if hex.slice(0..1) == '00'
35
+ private_key = MoneyTree::PrivateKey.new(key: hex.slice(2..-1))
36
+ {
37
+ private_key: private_key,
38
+ public_key: MoneyTree::PublicKey.new(private_key)
39
+ }
40
+ elsif %w(02 03).include? hex.slice(0..1)
41
+ { public_key: MoneyTree::PublicKey.new(hex) }
42
+ else
43
+ raise ImportError, 'Public or private key data does not match version type'
44
+ end
45
+ end
46
+
47
+ def is_private?
48
+ index >= 0x80000000 || index < 0
49
+ end
50
+
51
+ def index_hex(i = index)
52
+ if i < 0
53
+ [i].pack('l>').unpack('H*').first
54
+ else
55
+ i.to_s(16).rjust(8, "0")
56
+ end
57
+ end
58
+
59
+ def depth_hex(depth)
60
+ depth.to_s(16).rjust(2, "0")
61
+ end
62
+
63
+ def private_derivation_message(i)
64
+ "\x00" + private_key.to_bytes + i_as_bytes(i)
65
+ end
66
+
67
+ def public_derivation_message(i)
68
+ public_key.to_bytes << i_as_bytes(i)
69
+ end
70
+
71
+ def i_as_bytes(i)
72
+ [i].pack('N')
73
+ end
74
+
75
+ def derive_private_key(i = 0)
76
+ message = i >= 0x80000000 || i < 0 ? private_derivation_message(i) : public_derivation_message(i)
77
+ hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
78
+ left_int = left_from_hash(hash)
79
+ raise InvalidKeyForIndex, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
80
+ child_private_key = (left_int + private_key.to_i) % MoneyTree::Key::ORDER
81
+ raise InvalidKeyForIndex, 'equal to zero' if child_private_key == 0 # very low probability
82
+ child_chain_code = right_from_hash(hash)
83
+ return child_private_key, child_chain_code
84
+ end
85
+
86
+ def derive_public_key(i = 0)
87
+ raise PrivatePublicMismatch if i >= 0x80000000
88
+ message = public_derivation_message(i)
89
+ hash = hmac_sha512 hex_to_bytes(chain_code_hex), message
90
+ left_int = left_from_hash(hash)
91
+ raise InvalidKeyForIndex, 'greater than or equal to order' if left_int >= MoneyTree::Key::ORDER # very low probability
92
+ factor = BN.new left_int.to_s
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
+
99
+ raise InvalidKeyForIndex, 'at infinity' if child_public_key == 1/0.0 # very low probability
100
+ child_chain_code = right_from_hash(hash)
101
+ return child_public_key, child_chain_code
102
+ end
103
+
104
+ def left_from_hash(hash)
105
+ bytes_to_int hash.bytes.to_a[0..31]
106
+ end
107
+
108
+ def right_from_hash(hash)
109
+ bytes_to_int hash.bytes.to_a[32..-1]
110
+ end
111
+
112
+ def to_serialized_hex(type = :public, network: :bitcoin)
113
+ raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
114
+ version_key = type.to_sym == :private ? :extended_privkey_version : :extended_pubkey_version
115
+ hex = NETWORKS[network][version_key] # version (4 bytes)
116
+ hex += depth_hex(depth) # depth (1 byte)
117
+ hex += parent_fingerprint # fingerprint of key (4 bytes)
118
+ hex += index_hex(index) # child number i (4 bytes)
119
+ hex += chain_code_hex
120
+ hex += type.to_sym == :private ? "00#{private_key.to_hex}" : public_key.compressed.to_hex
121
+ end
122
+
123
+ def to_bip32(type = :public, network: :bitcoin)
124
+ raise PrivatePublicMismatch if type.to_sym == :private && private_key.nil?
125
+ to_serialized_base58 to_serialized_hex(type, network: network)
126
+ end
127
+
128
+ def to_serialized_address(type = :public, network: :bitcoin)
129
+ puts 'Node.to_serialized_address is DEPRECATED. Please use .to_bip32.'
130
+ to_bip32(type, network: network)
131
+ end
132
+
133
+ def to_identifier(compressed=true)
134
+ key = compressed ? public_key.compressed : public_key.uncompressed
135
+ key.to_ripemd160
136
+ end
137
+
138
+ def to_fingerprint
139
+ public_key.compressed.to_fingerprint
140
+ end
141
+
142
+ def parent_fingerprint
143
+ if @parent_fingerprint
144
+ @parent_fingerprint
145
+ else
146
+ depth.zero? ? '00000000' : parent.to_fingerprint
147
+ end
148
+ end
149
+
150
+ def to_address(compressed=true, network: :bitcoin)
151
+ address = NETWORKS[network][:address_version] + to_identifier(compressed)
152
+ to_serialized_base58 address
153
+ end
154
+
155
+ def subnode(i = 0, opts = {})
156
+ if private_key.nil?
157
+ child_public_key, child_chain_code = derive_public_key(i)
158
+ child_public_key = MoneyTree::PublicKey.new child_public_key
159
+ else
160
+ child_private_key, child_chain_code = derive_private_key(i)
161
+ child_private_key = MoneyTree::PrivateKey.new key: child_private_key
162
+ child_public_key = MoneyTree::PublicKey.new child_private_key
163
+ end
164
+
165
+ MoneyTree::Node.new( depth: depth+1,
166
+ index: i,
167
+ private_key: private_key.nil? ? nil : child_private_key,
168
+ public_key: child_public_key,
169
+ chain_code: child_chain_code,
170
+ parent: self)
171
+ end
172
+
173
+ # path: a path of subkeys denoted by numbers and slashes. Use
174
+ # p or i<0 for private key derivation. End with .pub to force
175
+ # the key public.
176
+ #
177
+ # Examples:
178
+ # 1p/-5/2/1 would call subkey(i=1, is_prime=True).subkey(i=-5).
179
+ # subkey(i=2).subkey(i=1) and then yield the private key
180
+ # 0/0/458.pub would call subkey(i=0).subkey(i=0).subkey(i=458) and
181
+ # then yield the public key
182
+ #
183
+ # You should choose either the p or the negative number convention for private key derivation.
184
+ def node_for_path(path)
185
+ force_public = path[-4..-1] == '.pub'
186
+ path = path[0..-5] if force_public
187
+ parts = path.split('/')
188
+ nodes = []
189
+ parts.each_with_index do |part, depth|
190
+ if part =~ /m/i
191
+ nodes << self
192
+ else
193
+ i = parse_index(part)
194
+ node = nodes.last || self
195
+ nodes << node.subnode(i)
196
+ end
197
+ end
198
+ if force_public or parts.first == 'M'
199
+ node = nodes.last
200
+ node.strip_private_info!
201
+ node
202
+ else
203
+ nodes.last
204
+ end
205
+ end
206
+
207
+ def parse_index(path_part)
208
+ is_prime = %w(p ').include? path_part[-1]
209
+ i = path_part.to_i
210
+
211
+ i = if i < 0
212
+ i
213
+ elsif is_prime
214
+ i | 0x80000000
215
+ else
216
+ i & 0x7fffffff
217
+ end
218
+ end
219
+
220
+ def strip_private_info!
221
+ @private_key = nil
222
+ end
223
+
224
+ def chain_code_hex
225
+ int_to_hex chain_code, 64
226
+ end
227
+ end
228
+
229
+ class Master < Node
230
+ module SeedGeneration
231
+ class Failure < Exception; end
232
+ class RNGFailure < Failure; end
233
+ class LengthFailure < Failure; end
234
+ class ValidityError < Failure; end
235
+ class ImportError < Failure; end
236
+ class TooManyAttempts < Failure; end
237
+ end
238
+
239
+ HD_WALLET_BASE_KEY = "Bitcoin seed"
240
+ RANDOM_SEED_SIZE = 32
241
+
242
+ attr_reader :seed, :seed_hash
243
+
244
+ def initialize(opts = {})
245
+ @depth = 0
246
+ @index = 0
247
+ opts[:seed] = [opts[:seed_hex]].pack("H*") if opts[:seed_hex]
248
+ if opts[:seed]
249
+ @seed = opts[:seed]
250
+ @seed_hash = generate_seed_hash(@seed)
251
+ raise SeedGeneration::ImportError unless seed_valid?(@seed_hash)
252
+ set_seeded_keys
253
+ elsif opts[:private_key] || opts[:public_key]
254
+ raise ImportError, 'chain code required' unless opts[:chain_code]
255
+ @chain_code = opts[:chain_code]
256
+ if opts[:private_key]
257
+ @private_key = opts[:private_key]
258
+ @public_key = MoneyTree::PublicKey.new @private_key
259
+ else opts[:public_key]
260
+ @public_key = if opts[:public_key].is_a?(MoneyTree::PublicKey)
261
+ opts[:public_key]
262
+ else
263
+ MoneyTree::PublicKey.new(opts[:public_key])
264
+ end
265
+ end
266
+ else
267
+ generate_seed
268
+ set_seeded_keys
269
+ end
270
+ end
271
+
272
+ def is_private?
273
+ true
274
+ end
275
+
276
+ def generate_seed
277
+ @seed = OpenSSL::Random.random_bytes(32)
278
+ @seed_hash = generate_seed_hash(@seed)
279
+ raise SeedGeneration::ValidityError unless seed_valid?(@seed_hash)
280
+ end
281
+
282
+ def generate_seed_hash(seed)
283
+ hmac_sha512 HD_WALLET_BASE_KEY, seed
284
+ end
285
+
286
+ def seed_valid?(seed_hash)
287
+ return false unless seed_hash.bytesize == 64
288
+ master_key = left_from_hash(seed_hash)
289
+ !master_key.zero? && master_key < MoneyTree::Key::ORDER
290
+ end
291
+
292
+ def set_seeded_keys
293
+ @private_key = MoneyTree::PrivateKey.new key: left_from_hash(seed_hash)
294
+ @chain_code = right_from_hash(seed_hash)
295
+ @public_key = MoneyTree::PublicKey.new @private_key
296
+ end
297
+
298
+ def seed_hex
299
+ bytes_to_hex(seed)
300
+ end
301
+ end
302
+ end
@@ -0,0 +1,127 @@
1
+ require 'openssl'
2
+ require 'base64'
3
+
4
+ module MoneyTree
5
+ module Support
6
+ include OpenSSL
7
+
8
+ INT32_MAX = 256 ** [1].pack("L*").size
9
+ INT64_MAX = 256 ** [1].pack("Q*").size
10
+ BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
11
+
12
+ def int_to_base58(int_val, leading_zero_bytes=0)
13
+ base58_val, base = '', BASE58_CHARS.size
14
+ while int_val > 0
15
+ int_val, remainder = int_val.divmod(base)
16
+ base58_val = BASE58_CHARS[remainder] + base58_val
17
+ end
18
+ base58_val
19
+ end
20
+
21
+ def base58_to_int(base58_val)
22
+ int_val, base = 0, BASE58_CHARS.size
23
+ base58_val.reverse.each_char.with_index do |char,index|
24
+ raise ArgumentError, 'Value not a valid Base58 String.' unless char_index = BASE58_CHARS.index(char)
25
+ int_val += char_index*(base**index)
26
+ end
27
+ int_val
28
+ end
29
+
30
+ def encode_base58(hex)
31
+ leading_zero_bytes = (hex.match(/^([0]+)/) ? $1 : '').size / 2
32
+ ("1"*leading_zero_bytes) + int_to_base58( hex.to_i(16) )
33
+ end
34
+
35
+ def decode_base58(base58_val)
36
+ s = base58_to_int(base58_val).to_s(16); s = (s.bytesize.odd? ? '0'+s : s)
37
+ s = '' if s == '00'
38
+ leading_zero_bytes = (base58_val.match(/^([1]+)/) ? $1 : '').size
39
+ s = ("00"*leading_zero_bytes) + s if leading_zero_bytes > 0
40
+ s
41
+ end
42
+ alias_method :base58_to_hex, :decode_base58
43
+
44
+ def to_serialized_base58(hex)
45
+ hash = sha256 hex
46
+ hash = sha256 hash
47
+ checksum = hash.slice(0..7)
48
+ address = hex + checksum
49
+ encode_base58 address
50
+ end
51
+
52
+ def from_serialized_base58(base58)
53
+ hex = decode_base58 base58
54
+ checksum = hex.slice!(-8..-1)
55
+ compare_checksum = sha256(sha256(hex)).slice(0..7)
56
+ raise EncodingError unless checksum == compare_checksum
57
+ hex
58
+ end
59
+
60
+ def digestify(digest_type, source, opts = {})
61
+ source = [source].pack("H*") unless opts[:ascii]
62
+ bytes_to_hex Digest.digest(digest_type, source)
63
+ end
64
+
65
+ def sha256(source, opts = {})
66
+ digestify('SHA256', source, opts)
67
+ end
68
+
69
+ def ripemd160(source, opts = {})
70
+ digestify('RIPEMD160', source, opts)
71
+ end
72
+
73
+ def encode_base64(hex)
74
+ Base64.encode64([hex].pack("H*")).chomp
75
+ end
76
+
77
+ def decode_base64(base64)
78
+ Base64.decode64(base64).unpack("H*")[0]
79
+ end
80
+
81
+ def hmac_sha512(key, message)
82
+ digest = Digest::SHA512.new
83
+ HMAC.digest digest, key, message
84
+ end
85
+
86
+ def hmac_sha512_hex(key, message)
87
+ md = hmac_sha512(key, message)
88
+ md.unpack("H*").first.rjust(64, '0')
89
+ end
90
+
91
+ def bytes_to_int(bytes, base = 16)
92
+ if bytes.is_a?(Array)
93
+ bytes = bytes.pack("C*")
94
+ end
95
+ bytes.unpack("H*")[0].to_i(16)
96
+ end
97
+
98
+ def int_to_hex(i, size=nil)
99
+ hex = i.to_s(16).downcase
100
+ if (hex.size % 2) != 0
101
+ hex = "#{0}#{hex}"
102
+ end
103
+
104
+ if size
105
+ hex.rjust(size, "0")
106
+ else
107
+ hex
108
+ end
109
+ end
110
+
111
+ def int_to_bytes(i)
112
+ [int_to_hex(i)].pack("H*")
113
+ end
114
+
115
+ def bytes_to_hex(bytes)
116
+ bytes.unpack("H*")[0].downcase
117
+ end
118
+
119
+ def hex_to_bytes(hex)
120
+ [hex].pack("H*")
121
+ end
122
+
123
+ def hex_to_int(hex)
124
+ hex.to_i(16)
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,3 @@
1
+ module MoneyTree
2
+ VERSION = "1.1"
3
+ end
data/lib/money-tree.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "openssl_extensions"
2
+ require "money-tree/version"
3
+ require "money-tree/support"
4
+ require "money-tree/networks"
5
+ require "money-tree/key"
6
+ require "money-tree/address"
7
+ require "money-tree/networks"
8
+ require "money-tree/node"
9
+
10
+ module MoneyTree
11
+
12
+ end
@@ -0,0 +1,33 @@
1
+ # encoding: ascii-8bit
2
+
3
+ require 'openssl'
4
+
5
+ module MoneyTree
6
+ module OpenSSLExtensions
7
+ def self.add(point_0, point_1)
8
+ validate_points(point_0, point_1)
9
+
10
+ group = OpenSSL::PKey::EC::Group.new('secp256k1')
11
+
12
+ point_0_hex = point_0.to_bn.to_s(16)
13
+ point_0_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_0_hex, 16))
14
+ point_1_hex = point_1.to_bn.to_s(16)
15
+ point_1_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_1_hex, 16))
16
+
17
+ sum_point = point_0_pt.add(point_1_pt)
18
+
19
+ sum_point.to_bn.to_s(16)
20
+ end
21
+
22
+ def self.validate_points(*points)
23
+ points.each do |point|
24
+ if !point.is_a?(OpenSSL::PKey::EC::Point)
25
+ raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
26
+ elsif point.infinity?
27
+ raise ArgumentError, "point must not be infinity"
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,38 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'money-tree/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "money-tree-openssl"
8
+ spec.version = MoneyTree::VERSION
9
+ spec.authors = ["Micah Winkelspecht"]
10
+ spec.email = ["winkelspecht@gmail.com"]
11
+ spec.description = %q{A Ruby Gem implementation of Bitcoin HD Wallets (forked to remove ffi)}
12
+ spec.summary = %q{Bitcoin Hierarchical Deterministic Wallets in Ruby! (Bitcoin standard BIP0032)}
13
+ spec.homepage = "https://github.com/cte/money-tree"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ # used with gem install ... -P HighSecurity
22
+ # spec.cert_chain = ["certs/mattatgemco.pem"]
23
+
24
+ # # Sign gem when evaluating spec with `gem` command
25
+ # # unless ENV has set a SKIP_GEM_SIGNING
26
+ # if ($0 =~ /gem\z/) and not ENV.include?("SKIP_GEM_SIGNING")
27
+ # spec.signing_key = File.join(Gem.user_home, ".ssh", "gem-private_key.pem")
28
+ # end
29
+
30
+ spec.add_dependency 'openssl', '>= 2.2'
31
+
32
+ spec.add_development_dependency "bundler"
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"
38
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe MoneyTree::Address do
4
+ describe "initialize" do
5
+ it "generates a private key by default" do
6
+ address = MoneyTree::Address.new
7
+ expect(address.private_key.key.length).to eql(64)
8
+ end
9
+
10
+ it "generates a public key by default" do
11
+ address = MoneyTree::Address.new
12
+ expect(address.public_key.key.length).to eql(66)
13
+ end
14
+
15
+ it "imports a private key in hex form" do
16
+ address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
17
+ expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
18
+ expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
19
+ expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
20
+ expect(address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
21
+ expect(address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
22
+ end
23
+
24
+ it "imports a private key in compressed wif format" do
25
+ address = MoneyTree::Address.new private_key: "KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK"
26
+ expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
27
+ expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
28
+ expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
29
+ end
30
+
31
+ it "imports a private key in uncompressed wif format" do
32
+ address = MoneyTree::Address.new private_key: "5JXz5ZyFk31oHVTQxqce7yitCmTAPxBqeGQ4b7H3Aj3L45wUhoa"
33
+ expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
34
+ expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
35
+ end
36
+ end
37
+
38
+ describe "to_s" do
39
+ before do
40
+ @address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
41
+ end
42
+
43
+ it "returns compressed base58 public key" do
44
+ expect(@address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
45
+ expect(@address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
46
+ end
47
+
48
+ it "returns compressed WIF private key" do
49
+ expect(@address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
50
+ end
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
+ expect(%w(m n)).to include(@address.to_s(network: :bitcoin_testnet)[0])
60
+ end
61
+ end
62
+ end