Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

openssl 3.0 support #325

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .devcontainer/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ARG RUBY_VERSION=3.2.2
FROM ghcr.io/rails/devcontainer/images/ruby:$RUBY_VERSION
26 changes: 26 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
{
"name": "Ruby Bitcoin Development",
"dockerFile": "Dockerfile",
"features": {
"ghcr.io/devcontainers/features/git:1": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},
"customizations": {
"vscode": {
"extensions": [
"Shopify.ruby-lsp"
],
"settings": {
"editor.formatOnSave": true,
"ruby.useBundler": true,
"ruby.useLanguageServer": true,
"ruby.lint": {
"rubocop": true
}
}
}
},
"forwardPorts": [],
"postCreateCommand": "bundle install",
"remoteUser": "vscode"
}
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ spec-rspec/examples.txt
# emacs
*~
\#*\#
.\#*
.\#*

# VS Code
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
34 changes: 25 additions & 9 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,24 @@ GEM
ast (2.4.0)
bacon (1.2.0)
byebug (10.0.2)
coderay (1.1.2)
coderay (1.1.3)
debug (1.9.2)
irb (~> 1.10)
reline (>= 0.3.8)
diff-lcs (1.3)
docile (1.3.1)
eventmachine (1.2.7)
ffi (1.9.25)
ffi-compiler (1.0.1)
ffi (>= 1.0.0)
ffi (1.17.0)
ffi-compiler (1.3.2)
ffi (>= 1.15.5)
rake
io-console (0.7.2)
irb (1.14.1)
rdoc (>= 4.0.0)
reline (>= 0.4.2)
jaro_winkler (1.5.1)
json (2.1.0)
method_source (0.9.0)
json (2.8.2)
method_source (0.9.2)
minitest (5.11.3)
parallel (1.12.1)
parser (2.5.1.2)
Expand All @@ -34,8 +41,14 @@ GEM
pry-byebug (3.6.0)
byebug (~> 10.0)
pry (~> 0.10)
psych (5.2.0)
stringio
rainbow (3.0.0)
rake (12.3.1)
rake (12.3.3)
rdoc (6.8.1)
psych (>= 4.0.0)
reline (0.5.11)
io-console (~> 0.5)
rspec (3.7.0)
rspec-core (~> 3.7.0)
rspec-expectations (~> 3.7.0)
Expand All @@ -58,13 +71,15 @@ GEM
ruby-progressbar (~> 1.7)
unicode-display_width (~> 1.0, >= 1.0.1)
ruby-progressbar (1.9.0)
scrypt (3.0.5)
scrypt (3.0.8)
ffi-compiler (>= 1.0, < 2.0)
rake (>= 9, < 14)
simplecov (0.16.1)
docile (~> 1.1)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
stringio (3.1.2)
unicode-display_width (1.4.0)

PLATFORMS
Expand All @@ -73,6 +88,7 @@ PLATFORMS
DEPENDENCIES
bacon (~> 1.2.0)
bitcoin-ruby!
debug
minitest (~> 5.11.3)
pry (~> 0.11.3)
pry-byebug (~> 3.6.0)
Expand All @@ -82,4 +98,4 @@ DEPENDENCIES
simplecov (~> 0.16.1)

BUNDLED WITH
1.17.3
2.5.23
4 changes: 4 additions & 0 deletions bitcoin-ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ Gem::Specification.new do |s|
s.add_runtime_dependency 'ffi'
s.add_runtime_dependency 'scrypt' # required by Litecoin
s.add_runtime_dependency 'eventmachine' # required for connection code

# Add development dependencies
s.add_development_dependency 'rspec', '~> 3.12'
s.add_development_dependency 'debug'
end
15 changes: 10 additions & 5 deletions lib/bitcoin.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module Bitcoin
# deprecated in favor of a unification of Fixnum and BigInteger named Integer.
# Since this project strivers for backwards-compatability, we determine the
# appropriate class to use at initialization.
#
#
# This avoids annoying deprecation warnings on newer versions for ourselves
# and library consumers.
Integer =
Expand All @@ -29,6 +29,7 @@ module Bitcoin
autoload :Script, 'bitcoin/script'
autoload :VERSION, 'bitcoin/version'
autoload :Key, 'bitcoin/key'
autoload :PKeyEC, 'bitcoin/pkey_ec'
autoload :ExtKey, 'bitcoin/ext_key'
autoload :ExtPubkey, 'bitcoin/ext_key'
autoload :Builder, 'bitcoin/builder'
Expand All @@ -39,6 +40,7 @@ module Bitcoin

autoload :ContractHash, 'bitcoin/contracthash'


module Trezor
autoload :Mnemonic, 'bitcoin/trezor/mnemonic'
end
Expand Down Expand Up @@ -286,11 +288,11 @@ def decode_target(target_bits)
end

def bitcoin_elliptic_curve
::OpenSSL::PKey::EC.new("secp256k1")
Bitcoin::PKeyEC.new
end

def generate_key
key = bitcoin_elliptic_curve.generate_key
key = Bitcoin::PKeyEC.generate_key
inspect_key( key )
end

Expand Down Expand Up @@ -409,11 +411,13 @@ def verify_signature(hash, signature, public_key)
def open_key(private_key, public_key=nil)
key = bitcoin_elliptic_curve
key.private_key = ::OpenSSL::BN.from_hex(private_key)
public_key = regenerate_public_key(private_key) unless public_key
key.public_key = ::OpenSSL::PKey::EC::Point.from_hex(key.group, public_key)
key
end

def group
OpenSSL::PKey::EC::Group.new('secp256k1')
end

def regenerate_public_key(private_key)
OpenSSL_EC.regenerate_key(private_key)[1]
end
Expand All @@ -439,6 +443,7 @@ def verify_message(address, signature, message)
return false unless valid_address?(address)
return false unless signature
return false unless signature.bytesize == 65

hash = bitcoin_signed_message_hash(message)
pubkey = OpenSSL_EC.recover_compact(hash, signature)
pubkey_to_address(pubkey) == address if pubkey
Expand Down
22 changes: 17 additions & 5 deletions lib/bitcoin/ffi/openssl.rb
Original file line number Diff line number Diff line change
Expand Up @@ -129,11 +129,23 @@ def self.regenerate_key(private_key)
private_key_hex = private_key.unpack('H*')[0]

group = OpenSSL::PKey::EC::Group.new('secp256k1')
key = OpenSSL::PKey::EC.new(group)
key.private_key = OpenSSL::BN.new(private_key_hex, 16)
key.public_key = group.generator.mul(key.private_key)

priv_hex = key.private_key.to_bn.to_s(16).downcase.rjust(64, '0')
private_key_bn = OpenSSL::BN.new(private_key_hex, 16)

# Generate public key point by multiplying generator with private key
public_key_point = group.generator.mul(private_key_bn)

# Create ASN1 structure for EC key
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Integer.new(1),
OpenSSL::ASN1::OctetString(private_key_bn.to_s(2)),
OpenSSL::ASN1::ObjectId('secp256k1', 0, :EXPLICIT),
OpenSSL::ASN1::BitString(public_key_point.to_octet_string(:uncompressed), 1, :EXPLICIT)
])

key = OpenSSL::PKey::EC.new(asn1.to_der)

# Verify the private key was generated correctly
priv_hex = key.private_key.to_s(16).downcase.rjust(64, '0')
if priv_hex != private_key_hex
raise 'regenerated wrong private_key, raise here before generating a faulty public_key too!'
end
Expand Down
8 changes: 4 additions & 4 deletions lib/bitcoin/key.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def initialize(privkey = nil, pubkey = nil, opts={compressed: true})

# Generate new priv/pub key.
def generate
@key.generate_key
@key = Bitcoin::PKeyEC.generate_key
end

# Get the private key (in hex).
Expand Down Expand Up @@ -155,7 +155,7 @@ def self.recover_compact_signature_to_key(data, signature_base64)

version = signature.unpack('C')[0]
return nil if version < 27 or version > 34

compressed = (version >= 31) ? (version -= 4; true) : false

hash = Bitcoin.bitcoin_signed_message_hash(data)
Expand Down Expand Up @@ -183,7 +183,7 @@ def to_bip38(passphrase)
addresshash = Digest::SHA256.digest( Digest::SHA256.digest( self.addr ) )[0...4]

require 'scrypt' unless defined?(::SCrypt::Engine)
buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
buf = SCrypt::Engine.scrypt(passphrase, addresshash, 16384, 8, 8, 64)
derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]

aes = proc{|k,a,b|
Expand Down Expand Up @@ -212,7 +212,7 @@ def self.from_bip38(encrypted_privkey, passphrase)
raise "Invalid checksum" unless Digest::SHA256.digest(Digest::SHA256.digest(version + flagbyte + addresshash + encryptedhalf1 + encryptedhalf2))[0...4] == checksum

require 'scrypt' unless defined?(::SCrypt::Engine)
buf = SCrypt::Engine.__sc_crypt(passphrase, addresshash, 16384, 8, 8, 64)
buf = SCrypt::Engine.scrypt(passphrase, addresshash, 16384, 8, 8, 64)
derivedhalf1, derivedhalf2 = buf[0...32], buf[32..-1]

aes = proc{|k,a|
Expand Down
67 changes: 67 additions & 0 deletions lib/bitcoin/pkey_ec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
module Bitcoin
class PKeyEC #< OpenSSL::PKey::EC

CURVE = 'secp256k1'

attr_reader :group, :private_key, :public_key


def private_key_hex; private_key.to_hex.rjust(64, '0'); end
def public_key_hex; public_key.to_hex.rjust(130, '0'); end

def initialize
@group = OpenSSL::PKey::EC::Group.new(CURVE)
end

def self.generate_key
OpenSSL::PKey::EC.generate(CURVE)
end

def public_key=(public_key_bn)
@public_key = public_key_bn
end

def private_key=(private_key_bn)
@public_key = restore_public_key(private_key_bn)
asn1 = OpenSSL::ASN1::Sequence(
[
OpenSSL::ASN1::Integer.new(1),
OpenSSL::ASN1::OctetString(private_key_bn.to_s(2)),
OpenSSL::ASN1::ObjectId(CURVE, 0, :EXPLICIT),
OpenSSL::ASN1::BitString(@public_key.to_octet_string(:uncompressed), 1, :EXPLICIT)
]
)
@pk = OpenSSL::PKey::EC.new(asn1.to_der)
@private_key = @pk.private_key
end

def dsa_sign_asn1(data)
@pk.dsa_sign_asn1(data)
end

def dsa_verify_asn1(data, signature)
initialize_from_public_key unless @pk
@pk.dsa_verify_asn1(data, signature)
end

def initialize_from_public_key
asn1 = OpenSSL::ASN1::Sequence.new(
[
OpenSSL::ASN1::Sequence.new([
OpenSSL::ASN1::ObjectId.new('id-ecPublicKey'),
OpenSSL::ASN1::ObjectId.new(@group.curve_name)
]),
OpenSSL::ASN1::BitString.new(@public_key.to_octet_string(:uncompressed))
]
)
@pk = OpenSSL::PKey::EC.new(asn1.to_der)
end

private

def restore_public_key(private_bn)
public_bn = group.generator.mul(private_bn).to_bn
public_bn = OpenSSL::PKey::EC::Point.new(@group, public_bn)
end
end
end
3 changes: 2 additions & 1 deletion spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
# Code coverage generation
require 'simplecov'

require 'debug'
SimpleCov.start do
add_group('Bitcoin') do |file|
['bitcoin.rb', 'opcodes.rb', 'script.rb', 'key.rb'].include?(
Expand Down Expand Up @@ -106,4 +107,4 @@
config.before(:each) do
Bitcoin.network = :bitcoin
end
end
end
15 changes: 8 additions & 7 deletions spec/unit/bitcoin/bitcoin_spec.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# frozen_string_literal: true

require 'pry'
require 'debug'
require 'spec_helper'

describe Bitcoin do
Expand Down Expand Up @@ -973,14 +973,16 @@

describe 'bitcoin base58 test vectors' do
# Port of Bitcoin Core test vectors.
# https://github.com/bitcoin/bitcoin/blob/595a7bab23bc21049526229054ea1fff1a29c0bf/src/test/base58_tests.cpp#L139
# https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp
let(:valid_base58_keys) do
JSON.parse(fixtures_file('base58_keys_valid.json'))
path = File.join(File.dirname(__FILE__), '../../fixtures/base58_keys_valid.json')
JSON.parse(File.read(path))
end
# Port of Bitcoin Core test vectors.
# https://github.com/bitcoin/bitcoin/blob/595a7bab23bc21049526229054ea1fff1a29c0bf/src/test/base58_tests.cpp#L179
# https://github.com/bitcoin/bitcoin/blob/master/src/test/base58_tests.cpp
let(:invalid_base58_keys) do
JSON.parse(fixtures_file('base58_keys_invalid.json'))
path = File.join(File.dirname(__FILE__), '../../fixtures/base58_keys_invalid.json')
JSON.parse(File.read(path))
end

it 'passes the valid keys cases' do
Expand Down Expand Up @@ -1014,8 +1016,7 @@
end

it 'fails the invalid keys cases' do
test_cases = JSON.parse(fixtures_file('base58_keys_invalid.json'))
test_cases.each do |test_case|
invalid_base58_keys.each do |test_case|
address = test_case[0]

%i[bitcoin testnet3 regtest].each do |network_name|
Expand Down
Loading