Skip to content

Commit f9b817c

Browse files
committed
handle openssl 1.1.0
* uses fixes from here se3000/ruby-eth#42 * replaces regenerate_key ffi code with pure-ruby openssl code. * removes unused der_to_private_key method.
1 parent f2c47a1 commit f9b817c

File tree

6 files changed

+135
-153
lines changed

6 files changed

+135
-153
lines changed

.travis.yml

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
language: ruby
22
rvm:
3-
- 2.2.9
4-
- 2.3.6
5-
- 2.4.3
6-
- 2.5.1
3+
- 2.4.9
4+
- 2.5.7
5+
- 2.6.5
76
script:
87
- bundle exec rake build_libsecp256k1
98
- bundle exec rake rspec

Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
PATH
22
remote: .
33
specs:
4-
bitcoin-ruby (0.0.18)
4+
bitcoin-ruby (0.0.20)
55
eventmachine
66
ffi
77
scrypt
@@ -82,4 +82,4 @@ DEPENDENCIES
8282
simplecov (~> 0.16.1)
8383

8484
BUNDLED WITH
85-
1.16.2
85+
1.17.3

README.rdoc

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,9 @@ Some of the main features are:
1313

1414
== Compatible with...
1515

16-
* ruby 1.9.3
17-
* ruby 2.0.0
18-
* ruby 2.1.2
19-
* ruby 2.2.0
20-
* ruby 2.2.2
16+
* ruby 2.4.x
17+
* ruby 2.5.x
18+
* ruby 2.6.x
2119

2220
== Installation
2321

lib/bitcoin/ffi/openssl.rb

Lines changed: 74 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,68 @@ module OpenSSL_EC # rubocop:disable Naming/ClassAndModuleCamelCase
1010
if FFI::Platform.windows?
1111
ffi_lib 'libeay32', 'ssleay32'
1212
else
13-
ffi_lib ['libssl.so.1.0.0', 'ssl']
13+
ffi_lib [
14+
'libssl.so.1.1.0', 'libssl.so.1.1',
15+
'libssl.so.1.0.0', 'libssl.so.10',
16+
'ssl'
17+
]
1418
end
1519

1620
NID_secp256k1 = 714 # rubocop:disable Naming/ConstantName
1721
POINT_CONVERSION_COMPRESSED = 2
1822
POINT_CONVERSION_UNCOMPRESSED = 4
1923

20-
attach_function :SSL_library_init, [], :int
21-
attach_function :ERR_load_crypto_strings, [], :void
22-
attach_function :SSL_load_error_strings, [], :void
24+
# OpenSSL 1.1.0 version as a numerical version value as defined in:
25+
# https://www.openssl.org/docs/man1.1.0/man3/OpenSSL_version.html
26+
VERSION_1_1_0_NUM = 0x10100000
27+
28+
# OpenSSL 1.1.0 engine constants, taken from:
29+
# https://github.com/openssl/openssl/blob/2be8c56a39b0ec2ec5af6ceaf729df154d784a43/include/openssl/crypto.h
30+
OPENSSL_INIT_ENGINE_RDRAND = 0x00000200
31+
OPENSSL_INIT_ENGINE_DYNAMIC = 0x00000400
32+
OPENSSL_INIT_ENGINE_CRYPTODEV = 0x00001000
33+
OPENSSL_INIT_ENGINE_CAPI = 0x00002000
34+
OPENSSL_INIT_ENGINE_PADLOCK = 0x00004000
35+
OPENSSL_INIT_ENGINE_ALL_BUILTIN = (
36+
OPENSSL_INIT_ENGINE_RDRAND |
37+
OPENSSL_INIT_ENGINE_DYNAMIC |
38+
OPENSSL_INIT_ENGINE_CRYPTODEV |
39+
OPENSSL_INIT_ENGINE_CAPI |
40+
OPENSSL_INIT_ENGINE_PADLOCK
41+
)
42+
43+
# OpenSSL 1.1.0 load strings constant, taken from:
44+
# https://github.com/openssl/openssl/blob/c162c126be342b8cd97996346598ecf7db56130f/include/openssl/ssl.h
45+
OPENSSL_INIT_LOAD_SSL_STRINGS = 0x00200000
46+
47+
# This is the very first function we need to use to determine what version
48+
# of OpenSSL we are interacting with.
49+
begin
50+
attach_function :OpenSSL_version_num, [], :ulong
51+
rescue FFI::NotFoundError
52+
attach_function :SSLeay, [], :long
53+
end
54+
55+
# Returns the version of SSL present.
56+
#
57+
# @return [Integer] version number as an integer.
58+
def self.version
59+
if self.respond_to?(:OpenSSL_version_num)
60+
OpenSSL_version_num()
61+
else
62+
SSLeay()
63+
end
64+
end
65+
66+
if version >= VERSION_1_1_0_NUM
67+
# Initialization procedure for the library was changed in OpenSSL 1.1.0
68+
attach_function :OPENSSL_init_ssl, [:uint64, :pointer], :int
69+
else
70+
attach_function :SSL_library_init, [], :int
71+
attach_function :ERR_load_crypto_strings, [], :void
72+
attach_function :SSL_load_error_strings, [], :void
73+
end
74+
2375
attach_function :RAND_poll, [], :int
2476

2577
attach_function :BN_CTX_free, [:pointer], :int
@@ -28,7 +80,6 @@ module OpenSSL_EC # rubocop:disable Naming/ClassAndModuleCamelCase
2880
attach_function :BN_bin2bn, %i[pointer int pointer], :pointer
2981
attach_function :BN_bn2bin, %i[pointer pointer], :int
3082
attach_function :BN_cmp, %i[pointer pointer], :int
31-
attach_function :BN_copy, %i[pointer pointer], :pointer
3283
attach_function :BN_dup, [:pointer], :pointer
3384
attach_function :BN_free, [:pointer], :int
3485
attach_function :BN_mod_inverse, %i[pointer pointer pointer pointer], :pointer
@@ -51,22 +102,17 @@ module OpenSSL_EC # rubocop:disable Naming/ClassAndModuleCamelCase
51102
attach_function :EC_KEY_set_private_key, %i[pointer pointer], :int
52103
attach_function :EC_KEY_set_public_key, %i[pointer pointer], :int
53104
attach_function :EC_POINT_free, [:pointer], :int
54-
attach_function :EC_POINT_is_at_infinity, %i[pointer pointer], :int
55105
attach_function :EC_POINT_mul, %i[pointer pointer pointer pointer pointer pointer], :int
56106
attach_function :EC_POINT_new, [:pointer], :pointer
57107
attach_function :EC_POINT_set_compressed_coordinates_GFp,
58108
%i[pointer pointer pointer int pointer], :int
59-
attach_function :d2i_ECPrivateKey, %i[pointer pointer long], :pointer
60-
attach_function :i2d_ECPrivateKey, %i[pointer pointer], :int
61109
attach_function :i2o_ECPublicKey, %i[pointer pointer], :uint
62-
attach_function :EC_KEY_check_key, [:pointer], :uint
63110
attach_function :ECDSA_do_sign, %i[pointer uint pointer], :pointer
64111
attach_function :BN_num_bits, [:pointer], :int
65112
attach_function :ECDSA_SIG_free, [:pointer], :void
66113
attach_function :EC_POINT_add, %i[pointer pointer pointer pointer pointer], :int
67114
attach_function :EC_POINT_point2hex, %i[pointer pointer int pointer], :string
68115
attach_function :EC_POINT_hex2point, %i[pointer string pointer pointer], :pointer
69-
attach_function :ECDSA_SIG_new, [], :pointer
70116
attach_function :d2i_ECDSA_SIG, %i[pointer pointer long], :pointer
71117
attach_function :i2d_ECDSA_SIG, %i[pointer pointer], :int
72118
attach_function :OPENSSL_free, :CRYPTO_free, [:pointer], :void
@@ -82,68 +128,17 @@ def self.regenerate_key(private_key)
82128
private_key = [private_key].pack('H*') if private_key.bytesize >= (32 * 2)
83129
private_key_hex = private_key.unpack('H*')[0]
84130

85-
# private_key = FFI::MemoryPointer.new(:uint8, private_key.bytesize)
86-
# .put_bytes(0, private_key, 0, private_key.bytesize)
87-
private_key = FFI::MemoryPointer.from_string(private_key)
88-
89-
init_ffi_ssl
90-
eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
91-
# priv_key = BN_bin2bn(private_key, private_key.size, BN_new())
92-
priv_key = BN_bin2bn(private_key, private_key.size - 1, BN_new())
93-
94-
group = EC_KEY_get0_group(eckey)
95-
order = BN_new()
96-
ctx = BN_CTX_new()
97-
EC_GROUP_get_order(group, order, ctx)
98-
99-
pub_key = EC_POINT_new(group)
100-
EC_POINT_mul(group, pub_key, priv_key, nil, nil, ctx)
101-
EC_KEY_set_private_key(eckey, priv_key)
102-
EC_KEY_set_public_key(eckey, pub_key)
103-
104-
BN_free(order)
105-
BN_CTX_free(ctx)
106-
EC_POINT_free(pub_key)
107-
BN_free(priv_key)
108-
109-
length = i2d_ECPrivateKey(eckey, nil)
110-
buf = FFI::MemoryPointer.new(:uint8, length)
111-
ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
112-
priv_hex = if i2d_ECPrivateKey(eckey, ptr) == length
113-
size = buf.get_array_of_uint8(8, 1)[0]
114-
buf.get_array_of_uint8(9, size).pack('C*').rjust(32, "\x00").unpack('H*')[0]
115-
# der_to_private_key( ptr.read_pointer.read_string(length).unpack("H*")[0] )
116-
end
131+
group = OpenSSL::PKey::EC::Group.new('secp256k1')
132+
key = OpenSSL::PKey::EC.new(group)
133+
key.private_key = OpenSSL::BN.new(private_key_hex, 16)
134+
key.public_key = group.generator.mul(key.private_key)
117135

136+
priv_hex = key.private_key.to_bn.to_s(16).downcase.rjust(64, '0')
118137
if priv_hex != private_key_hex
119138
raise 'regenerated wrong private_key, raise here before generating a faulty public_key too!'
120139
end
121140

122-
length = i2o_ECPublicKey(eckey, nil)
123-
buf = FFI::MemoryPointer.new(:uint8, length)
124-
ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
125-
pub_hex = buf.read_string(length).unpack('H*')[0] if i2o_ECPublicKey(eckey, ptr) == length
126-
127-
EC_KEY_free(eckey)
128-
129-
[priv_hex, pub_hex]
130-
end
131-
132-
# extract private key from uncompressed DER format
133-
def self.der_to_private_key(der_hex)
134-
init_ffi_ssl
135-
# k = EC_KEY_new_by_curve_name(NID_secp256k1)
136-
# kp = FFI::MemoryPointer.new(:pointer).put_pointer(0, eckey)
137-
138-
buf = FFI::MemoryPointer.from_string([der_hex].pack('H*'))
139-
ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
140-
141-
# ec_key = d2i_ECPrivateKey(kp, ptr, buf.size-1)
142-
ec_key = d2i_ECPrivateKey(nil, ptr, buf.size - 1)
143-
return nil if ec_key.null?
144-
bn = EC_KEY_get0_private_key(ec_key)
145-
BN_bn2bin(bn, buf)
146-
buf.read_string(32).unpack('H*')[0]
141+
[priv_hex, key.public_key.to_bn.to_s(16).downcase]
147142
end
148143

149144
# Given the components of a signature and a selector value, recover and
@@ -395,9 +390,18 @@ def self.repack_der_signature(signature)
395390
def self.init_ffi_ssl
396391
@ssl_loaded ||= false
397392
return if @ssl_loaded
398-
SSL_library_init()
399-
ERR_load_crypto_strings()
400-
SSL_load_error_strings()
393+
394+
if version >= VERSION_1_1_0_NUM
395+
OPENSSL_init_ssl(
396+
OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_ENGINE_ALL_BUILTIN,
397+
nil
398+
)
399+
else
400+
SSL_library_init()
401+
ERR_load_crypto_strings()
402+
SSL_load_error_strings()
403+
end
404+
401405
RAND_poll()
402406
@ssl_loaded = true
403407
end

lib/bitcoin/version.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
module Bitcoin
2-
VERSION = "0.0.19"
2+
VERSION = "0.0.20"
33
end

spec/unit/bitcoin/bitcoin_spec.rb

Lines changed: 52 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -772,82 +772,63 @@
772772
end
773773
end
774774

775-
describe '.der_to_private_key' do
776-
it 'extracts the private key from uncompressed DER format' do
777-
der =
778-
'308201130201010420a29fe0f28b2936dbc89f889f74cd1f0662d18a873ac15d6c' \
779-
'd417b808db1ccd0aa081a53081a2020101302c06072a8648ce3d0101022100ffff' \
780-
'fffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604' \
781-
'010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959' \
782-
'f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47' \
783-
'd08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03b' \
784-
'bfd25e8cd0364141020101a14403420004768cfc6c44b927b0e69e9dd343e96132' \
785-
'f7cd1d360d8cb8d65c83d89d7beaceadfd19918e076606a099344156acdb026b10' \
786-
'65a958e39f098cfd0a34dd976291d6'
787-
788-
expect(
789-
Bitcoin::OpenSSL_EC.der_to_private_key(der)
790-
).to eq('a29fe0f28b2936dbc89f889f74cd1f0662d18a873ac15d6cd417b808db1ccd0a')
775+
describe 'signing and verifying messages' do
776+
context 'testnet' do
777+
before { Bitcoin.network = :testnet3 }
778+
779+
it 'verifies the signature of a testnet address' do
780+
expect(
781+
Bitcoin.verify_message(
782+
'mwPVMbZQgkpwJJt2YP3sLSgbEBQw3FWZSc',
783+
'H5GER0Nz+L7TPZMQzXtv0hnLSsyfPok9lkdHIv01vksREpEpOhTPTonU1xvy' \
784+
'PAOIIKhU3++Ol+LaWKWmsfyxDXk=',
785+
'A' * 500
786+
)
787+
).to be true
788+
end
791789
end
792790

793-
describe 'signing and verifying messages' do
794-
context 'testnet' do
795-
before { Bitcoin.network = :testnet3 }
796-
797-
it 'verifies the signature of a testnet address' do
798-
expect(
799-
Bitcoin.verify_message(
800-
'mwPVMbZQgkpwJJt2YP3sLSgbEBQw3FWZSc',
801-
'H5GER0Nz+L7TPZMQzXtv0hnLSsyfPok9lkdHIv01vksREpEpOhTPTonU1xvy' \
802-
'PAOIIKhU3++Ol+LaWKWmsfyxDXk=',
803-
'A' * 500
804-
)
805-
).to be true
806-
end
791+
context 'mainnet' do
792+
before { Bitcoin.network = :bitcoin }
793+
let(:address_and_keys1) do
794+
%w[
795+
1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ
796+
12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747
797+
040b4c866585dd868a9d62348a9cd008d6a312937048fff31670e7e920cfc7a7 \
798+
447b5f0bba9e01e6fe4735c8383e6e7a3347a0fd72381b8f797a19f694054e5a69
799+
]
800+
end
801+
let(:address_and_keys2) do
802+
%w[
803+
1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs
804+
12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747
805+
030b4c866585dd868a9d62348a9cd008d6a312937048fff31670e7e920cfc7a744
806+
]
807807
end
808808

809-
context 'mainnet' do
810-
before { Bitcoin.network = :bitcoin }
811-
let(:address_and_keys1) do
812-
%w[
813-
1QFqqMUD55ZV3PJEJZtaKCsQmjLT6JkjvJ
814-
12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747
815-
040b4c866585dd868a9d62348a9cd008d6a312937048fff31670e7e920cfc7a7 \
816-
447b5f0bba9e01e6fe4735c8383e6e7a3347a0fd72381b8f797a19f694054e5a69
817-
]
818-
end
819-
let(:address_and_keys2) do
820-
%w[
821-
1NoJrossxPBKfCHuJXT4HadJrXRE9Fxiqs
822-
12b004fff7f4b69ef8650e767f18f11ede158148b425660723b9f9a66e61f747
823-
030b4c866585dd868a9d62348a9cd008d6a312937048fff31670e7e920cfc7a744
824-
]
825-
end
826-
827-
it 'successfully signs and verifies the message' do
828-
[address_and_keys1, address_and_keys2].each do |_addr, privkey, _pubkey|
829-
key = Bitcoin.open_key(privkey)
830-
16.times.each do |count|
831-
signature = Bitcoin.sign_message(
832-
key.private_key_hex,
833-
key.public_key_hex,
834-
format('Very secret message %<count>d: 11', count: count)
809+
it 'successfully signs and verifies the message' do
810+
[address_and_keys1, address_and_keys2].each do |_addr, privkey, _pubkey|
811+
key = Bitcoin.open_key(privkey)
812+
16.times.each do |count|
813+
signature = Bitcoin.sign_message(
814+
key.private_key_hex,
815+
key.public_key_hex,
816+
format('Very secret message %<count>d: 11', count: count)
817+
)
818+
expect(
819+
Bitcoin.verify_message(
820+
signature['address'],
821+
'invalid-signature',
822+
signature['message']
823+
)
824+
).to be false
825+
expect(
826+
Bitcoin.verify_message(
827+
signature['address'],
828+
signature['signature'],
829+
signature['message']
835830
)
836-
expect(
837-
Bitcoin.verify_message(
838-
signature['address'],
839-
'invalid-signature',
840-
signature['message']
841-
)
842-
).to be false
843-
expect(
844-
Bitcoin.verify_message(
845-
signature['address'],
846-
signature['signature'],
847-
signature['message']
848-
)
849-
).to be true
850-
end
831+
).to be true
851832
end
852833
end
853834
end

0 commit comments

Comments
 (0)