Skip to content

Commit bac97ad

Browse files
authored
spec: add more openssl tests (#49)
* spec: ensure addresses to be instance of self * lib: trim whitespaces * spec: trim whitespaces * spec: add wobine's blackboard series' keys * spec: add tests for validating points * spec: add bitcoin wiki keys
1 parent 8eb07b4 commit bac97ad

File tree

9 files changed

+117
-88
lines changed

9 files changed

+117
-88
lines changed

lib/money-tree.rb

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,4 @@
88
require "money-tree/node"
99

1010
module MoneyTree
11-
1211
end

lib/money-tree/address.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
module MoneyTree
22
class Address
33
attr_reader :private_key, :public_key
4-
4+
55
def initialize(opts = {})
66
private_key = opts.delete(:private_key)
77
@private_key = MoneyTree::PrivateKey.new({ key: private_key }.merge(opts))
88
@public_key = MoneyTree::PublicKey.new(@private_key, opts)
99
end
10-
10+
1111
def to_s(network: :bitcoin)
1212
public_key.to_s(network: network)
1313
end

lib/money-tree/support.rb

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@
44
module MoneyTree
55
module Support
66
include OpenSSL
7-
7+
88
INT32_MAX = 256 ** [1].pack("L*").size
99
INT64_MAX = 256 ** [1].pack("Q*").size
1010
BASE58_CHARS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
11-
11+
1212
def int_to_base58(int_val, leading_zero_bytes=0)
1313
base58_val, base = '', BASE58_CHARS.size
1414
while int_val > 0
@@ -40,61 +40,61 @@ def decode_base58(base58_val)
4040
s
4141
end
4242
alias_method :base58_to_hex, :decode_base58
43-
43+
4444
def to_serialized_base58(hex)
4545
hash = sha256 hex
4646
hash = sha256 hash
4747
checksum = hash.slice(0..7)
4848
address = hex + checksum
4949
encode_base58 address
5050
end
51-
51+
5252
def from_serialized_base58(base58)
5353
hex = decode_base58 base58
5454
checksum = hex.slice!(-8..-1)
5555
compare_checksum = sha256(sha256(hex)).slice(0..7)
5656
raise EncodingError unless checksum == compare_checksum
5757
hex
5858
end
59-
59+
6060
def digestify(digest_type, source, opts = {})
6161
source = [source].pack("H*") unless opts[:ascii]
6262
bytes_to_hex Digest.digest(digest_type, source)
6363
end
64-
64+
6565
def sha256(source, opts = {})
6666
digestify('SHA256', source, opts)
6767
end
68-
68+
6969
def ripemd160(source, opts = {})
7070
digestify('RIPEMD160', source, opts)
7171
end
72-
72+
7373
def encode_base64(hex)
7474
Base64.encode64([hex].pack("H*")).chomp
7575
end
76-
76+
7777
def decode_base64(base64)
7878
Base64.decode64(base64).unpack("H*")[0]
7979
end
80-
80+
8181
def hmac_sha512(key, message)
8282
digest = Digest::SHA512.new
8383
HMAC.digest digest, key, message
8484
end
85-
85+
8686
def hmac_sha512_hex(key, message)
8787
md = hmac_sha512(key, message)
8888
md.unpack("H*").first.rjust(64, '0')
8989
end
90-
90+
9191
def bytes_to_int(bytes, base = 16)
9292
if bytes.is_a?(Array)
9393
bytes = bytes.pack("C*")
9494
end
9595
bytes.unpack("H*")[0].to_i(16)
9696
end
97-
97+
9898
def int_to_hex(i, size=nil)
9999
hex = i.to_s(16).downcase
100100
if (hex.size % 2) != 0
@@ -107,19 +107,19 @@ def int_to_hex(i, size=nil)
107107
hex
108108
end
109109
end
110-
110+
111111
def int_to_bytes(i)
112112
[int_to_hex(i)].pack("H*")
113113
end
114-
114+
115115
def bytes_to_hex(bytes)
116116
bytes.unpack("H*")[0].downcase
117117
end
118-
118+
119119
def hex_to_bytes(hex)
120120
[hex].pack("H*")
121121
end
122-
122+
123123
def hex_to_int(hex)
124124
hex.to_i(16)
125125
end

lib/openssl_extensions.rb

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,23 @@ module MoneyTree
66
module OpenSSLExtensions
77
def self.add(point_0, point_1)
88
validate_points(point_0, point_1)
9-
109
group = OpenSSL::PKey::EC::Group.new('secp256k1')
11-
1210
point_0_hex = point_0.to_bn.to_s(16)
1311
point_0_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_0_hex, 16))
1412
point_1_hex = point_1.to_bn.to_s(16)
1513
point_1_pt = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(point_1_hex, 16))
16-
1714
sum_point = point_0_pt.add(point_1_pt)
18-
1915
sum_point.to_bn.to_s(16)
2016
end
2117

2218
def self.validate_points(*points)
2319
points.each do |point|
2420
if !point.is_a?(OpenSSL::PKey::EC::Point)
25-
raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
21+
raise ArgumentError, "point must be an OpenSSL::PKey::EC::Point object"
2622
elsif point.infinity?
27-
raise ArgumentError, "point must not be infinity"
23+
raise ArgumentError, "point must not be infinity"
2824
end
2925
end
3026
end
3127
end
3228
end
33-

spec/lib/money-tree/address_spec.rb

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,20 @@
44
describe "initialize" do
55
it "generates a private key by default" do
66
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
710
expect(address.private_key.key.length).to eql(64)
811
end
9-
12+
1013
it "generates a public key by default" do
1114
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
1218
expect(address.public_key.key.length).to eql(66)
1319
end
14-
20+
1521
it "imports a private key in hex form" do
1622
address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
1723
expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
@@ -20,36 +26,46 @@
2026
expect(address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
2127
expect(address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
2228
end
23-
29+
2430
it "imports a private key in compressed wif format" do
2531
address = MoneyTree::Address.new private_key: "KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK"
2632
expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
2733
expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
2834
expect(address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
2935
end
30-
36+
3137
it "imports a private key in uncompressed wif format" do
3238
address = MoneyTree::Address.new private_key: "5JXz5ZyFk31oHVTQxqce7yitCmTAPxBqeGQ4b7H3Aj3L45wUhoa"
3339
expect(address.private_key.key).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
3440
expect(address.public_key.key).to eql("022dfc2557a007c93092c2915f11e8aa70c4f399a6753e2e908330014091580e4b")
3541
end
3642
end
37-
43+
3844
describe "to_s" do
3945
before do
4046
@address = MoneyTree::Address.new private_key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
4147
end
42-
48+
4349
it "returns compressed base58 public key" do
4450
expect(@address.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
4551
expect(@address.public_key.to_s).to eql("13uVqa35BMo4mYq9LiZrXVzoz9EFZ6aoXe")
4652
end
47-
53+
4854
it "returns compressed WIF private key" do
4955
expect(@address.private_key.to_s).to eql("KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK")
5056
end
5157
end
5258

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+
5369
context "testnet3" do
5470
before do
5571
@address = MoneyTree::Address.new network: :bitcoin_testnet

spec/lib/money-tree/openssl_extensions_spec.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,13 @@
4242
expect { MoneyTree::OpenSSLExtensions.add(0, point_2) }.to raise_error(ArgumentError)
4343
expect { MoneyTree::OpenSSLExtensions.add(point_infinity, point_2) }.to raise_error(ArgumentError)
4444
expect { MoneyTree::OpenSSLExtensions.add(point_1, point_2) }.to_not raise_error
45+
expect { MoneyTree::OpenSSLExtensions.validate_points(point_1) }.to_not raise_error
46+
end
47+
48+
it "validates points correctly" do
49+
expect { MoneyTree::OpenSSLExtensions.validate_points(point_1) }.to_not raise_error
50+
expect { MoneyTree::OpenSSLExtensions.validate_points(point_2) }.to_not raise_error
51+
expect { MoneyTree::OpenSSLExtensions.validate_points(point_infinity) }.to raise_error(ArgumentError)
4552
end
4653

4754
it "should add points correctly" do

spec/lib/money-tree/private_key_spec.rb

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
before do
55
@key = MoneyTree::PrivateKey.new key: "5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b"
66
end
7-
7+
88
describe "to_hex" do
99
it "has 64 characters" do
1010
# must always be 64 characters - leading zeroes need to be preserved!
@@ -15,81 +15,81 @@
1515
master = MoneyTree::Master.new seed_hex: "9cf6b6e8451c7d551cb402e2997566e5c7c258543eadb184f9f39322b2e6959b"
1616
expect(master.node_for_path("m/427").private_key.to_hex.length).to eql(64)
1717
end
18-
18+
1919
it "is a valid hex" do
20-
expect(@key.to_hex).to eql('5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b' )
20+
expect(@key.to_hex).to eql('5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b')
2121
end
2222
end
23-
23+
2424
describe "to_wif" do
2525
it "is a 52 character base58 key" do
2626
expect(@key.to_wif.length).to eql(52)
2727
end
28-
28+
2929
it "starts with K or L" do
3030
expect(%w(K L)).to include(@key.to_wif[0])
3131
end
32-
32+
3333
it "is a valid compressed wif" do
34-
expect(@key.to_wif).to eql('KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK' )
34+
expect(@key.to_wif).to eql('KzPkwAXJ4wtXHnbamTaJqoMrzwCUUJaqhUxnqYhnZvZH6KhgmDPK')
3535
end
3636
end
37-
37+
3838
describe "to_wif(compressed: false)" do
3939
it "is a 51 character base58 key" do
4040
expect(@key.to_wif(compressed: false).length).to eql(51)
4141
end
42-
42+
4343
it "starts with 5" do
4444
expect(@key.to_wif(compressed: false)[0]).to eql('5')
4545
end
46-
46+
4747
it "is valid" do
4848
expect(@key.to_wif(compressed: false)).to eql('5JXz5ZyFk31oHVTQxqce7yitCmTAPxBqeGQ4b7H3Aj3L45wUhoa')
4949
end
5050
end
51-
51+
5252
describe "from_wif(wif)" do
5353
it "returns the key from a wif" do
5454
expect(@key.from_wif("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTLvyTJ")).to eql('0c28fca386c7a227600b2fe50b7cae11ec86d3bf1fbe471be89827e19d72aa1d')
5555
end
56-
56+
5757
it "raises an error on bad checksum" do
5858
expect { @key.from_wif("5HueCGU8rMjxEXxiPuD5BDku4MkFqeZyd4dZ1jvhTVqvbTBADTJ") }.to raise_error(MoneyTree::Key::InvalidWIFFormat)
5959
end
6060
end
61-
61+
6262
describe "to_base64" do
6363
it "has 44 characters" do
6464
expect(@key.to_base64.length).to eql(44)
6565
end
66-
66+
6767
it "is a valid base64" do
68-
expect(@key.to_base64).to eql('Xq5Tdftfeg6mUFZjY776KDDvRBvcsZGYrfMY+u6G1ks=' )
68+
expect(@key.to_base64).to eql('Xq5Tdftfeg6mUFZjY776KDDvRBvcsZGYrfMY+u6G1ks=')
6969
end
7070
end
71-
71+
7272
describe "from_base64(base64_key)" do
7373
it "parses base64 key" do
7474
@key = MoneyTree::PrivateKey.new(key: "Xq5Tdftfeg6mUFZjY776KDDvRBvcsZGYrfMY+u6G1ks=")
7575
expect(@key.to_hex).to eql("5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b")
7676
end
77-
77+
7878
it "returns the key from base64 encoding" do
7979
expect(@key.from_base64("Xq5Tdftfeg6mUFZjY776KDDvRBvcsZGYrfMY+u6G1ks=")).to eql('5eae5375fb5f7a0ea650566363befa2830ef441bdcb19198adf318faee86d64b')
8080
end
81-
81+
8282
it "raises an error on bad encoding" do
8383
expect { @key.from_base64("Xq5Tdftfeg6mUFZjY776KD&%#BbadBADrfMY+u6G1ks=") }.to raise_error(MoneyTree::Key::InvalidBase64Format)
8484
end
8585
end
86-
86+
8787
describe "valid?(eckey)" do
8888
it "checks for a valid key" do
8989
expect(@key.valid?).to be_truthy
9090
end
9191
end
92-
92+
9393
describe "parse_raw_key" do
9494
it "returns error if key is not Bignum, hex, base64, or wif formatted" do
9595
expect { @key = MoneyTree::PrivateKey.new(key: "Thisisnotakey") }.to raise_error(MoneyTree::Key::KeyFormatNotFound)

0 commit comments

Comments
 (0)