class Eth::OpenSsl

Constants

NID_secp256k1
OPENSSL_INIT_ENGINE_ALL_BUILTIN
OPENSSL_INIT_ENGINE_CAPI
OPENSSL_INIT_ENGINE_CRYPTODEV
OPENSSL_INIT_ENGINE_DYNAMIC
OPENSSL_INIT_ENGINE_PADLOCK
OPENSSL_INIT_ENGINE_RDRAND

OpenSSL 1.1.0 engine constants, taken from: github.com/openssl/openssl/blob/2be8c56a39b0ec2ec5af6ceaf729df154d784a43/include/openssl/crypto.h

OPENSSL_INIT_LOAD_SSL_STRINGS

OpenSSL 1.1.0 load strings constant, taken from: github.com/openssl/openssl/blob/c162c126be342b8cd97996346598ecf7db56130f/include/openssl/ssl.h

POINT_CONVERSION_COMPRESSED
POINT_CONVERSION_UNCOMPRESSED
VERSION_1_1_0_NUM

OpenSSL 1.1.0 version as a numerical version value as defined in: www.openssl.org/docs/man1.1.0/man3/OpenSSL_version.html

Public Class Methods

BN_num_bytes(ptr) click to toggle source
# File lib/eth/open_ssl.rb, line 108
def BN_num_bytes(ptr)
  (BN_num_bits(ptr) + 7) / 8
end
init_ffi_ssl() click to toggle source
# File lib/eth/open_ssl.rb, line 225
def init_ffi_ssl
  return if @ssl_loaded
  if version >= VERSION_1_1_0_NUM
    OPENSSL_init_ssl(
      OPENSSL_INIT_LOAD_SSL_STRINGS | OPENSSL_INIT_ENGINE_ALL_BUILTIN,
      nil
    )
  else
    SSL_library_init()
    ERR_load_crypto_strings()
    SSL_load_error_strings()
  end

  RAND_poll()
  @ssl_loaded = true
end
recover_compact(hash, signature) click to toggle source
# File lib/eth/open_ssl.rb, line 207
def recover_compact(hash, signature)
  return false if signature.bytesize != 65

  version = signature.unpack('C')[0]

  # Version of signature should be 27 or 28, but 0 and 1 are also possible versions
  # which can show up in Ledger hardwallet signings
  if version < 27
    version += 27
  end

  v_base = Eth.replayable_v?(version) ? Eth.replayable_chain_id : Eth.v_base

  return false if version < v_base

  recover_public_key_from_signature(hash, signature, (version - v_base), false)
end
recover_public_key_from_signature(message_hash, signature, rec_id, is_compressed) click to toggle source
# File lib/eth/open_ssl.rb, line 153
def recover_public_key_from_signature(message_hash, signature, rec_id, is_compressed)
  return nil if rec_id < 0 or signature.bytesize != 65
  init_ffi_ssl

  signature = FFI::MemoryPointer.from_string(signature)
  r = BN_bin2bn(signature[1], 32, BN_new())
  s = BN_bin2bn(signature[33], 32, BN_new())

  _n, i = 0, rec_id / 2
  eckey = EC_KEY_new_by_curve_name(NID_secp256k1)

  EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED) if is_compressed

  group = EC_KEY_get0_group(eckey)
  order = BN_new()
  EC_GROUP_get_order(group, order, nil)
  x = BN_dup(order)
  BN_mul_word(x, i)
  BN_add(x, x, r)

  field = BN_new()
  EC_GROUP_get_curve_GFp(group, field, nil, nil, nil)

  if BN_cmp(x, field) >= 0
    bn_free_each r, s, order, x, field
    EC_KEY_free(eckey)
    return nil
  end

  big_r = EC_POINT_new(group)
  EC_POINT_set_compressed_coordinates_GFp(group, big_r, x, rec_id % 2, nil)

  big_q = EC_POINT_new(group)
  n = EC_GROUP_get_degree(group)
  e = BN_bin2bn(message_hash, message_hash.bytesize, BN_new())
  BN_rshift(e, e, 8 - (n & 7)) if 8 * message_hash.bytesize > n

  ctx = BN_CTX_new()
  zero, rr, sor, eor = BN_new(), BN_new(), BN_new(), BN_new()
  BN_set_word(zero, 0)
  BN_mod_sub(e, zero, e, order, ctx)
  BN_mod_inverse(rr, r, order, ctx)
  BN_mod_mul(sor, s, rr, order, ctx)
  BN_mod_mul(eor, e, rr, order, ctx)
  EC_POINT_mul(group, big_q, eor, big_r, sor, ctx)
  EC_KEY_set_public_key(eckey, big_q)
  BN_CTX_free(ctx)

  bn_free_each r, s, order, x, field, e, zero, rr, sor, eor
  [big_r, big_q].each{|j| EC_POINT_free(j) }

  recover_public_hex eckey
end
sign_compact(hash, private_key, public_key_hex) click to toggle source
# File lib/eth/open_ssl.rb, line 112
def sign_compact(hash, private_key, public_key_hex)
  private_key = [private_key].pack("H*") if private_key.bytesize >= 64
  pubkey_compressed = false

  init_ffi_ssl
  eckey = EC_KEY_new_by_curve_name(NID_secp256k1)
  priv_key = BN_bin2bn(private_key, private_key.bytesize, BN_new())

  group, order, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_CTX_new()
  EC_GROUP_get_order(group, order, ctx)

  pub_key = EC_POINT_new(group)
  EC_POINT_mul(group, pub_key, priv_key, nil, nil, ctx)
  EC_KEY_set_private_key(eckey, priv_key)
  EC_KEY_set_public_key(eckey, pub_key)

  signature = ECDSA_do_sign(hash, hash.bytesize, eckey)

  BN_free(order)
  BN_CTX_free(ctx)
  EC_POINT_free(pub_key)
  BN_free(priv_key)
  EC_KEY_free(eckey)

  buf, rec_id, head = FFI::MemoryPointer.new(:uint8, 32), nil, nil
  r, s = signature.get_array_of_pointer(0, 2).map{|i| BN_bn2bin(i, buf); buf.read_string(BN_num_bytes(i)).rjust(32, "\x00") }

  if signature.get_array_of_pointer(0, 2).all?{|i| BN_num_bits(i) <= 256 }
    4.times{|i|
      head = [ Eth.v_base + i ].pack("C")
      if public_key_hex == recover_public_key_from_signature(hash, [head, r, s].join, i, pubkey_compressed)
        rec_id = i; break
      end
    }
  end

  ECDSA_SIG_free(signature)

  [ head, [r,s] ].join if rec_id
end
version() click to toggle source

Returns the version of SSL present.

@return [Integer] version number as an integer.

# File lib/eth/open_ssl.rb, line 56
def self.version
  if self.respond_to?(:OpenSSL_version_num)
    OpenSSL_version_num()
  else
    SSLeay()
  end
end

Private Class Methods

bn_free_each(*list) click to toggle source
# File lib/eth/open_ssl.rb, line 245
def bn_free_each(*list)
  list.each{|j| BN_free(j) }
end
recover_public_hex(eckey) click to toggle source
# File lib/eth/open_ssl.rb, line 249
def recover_public_hex(eckey)
  length = i2o_ECPublicKey(eckey, nil)
  buf = FFI::MemoryPointer.new(:uint8, length)
  ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf)
  pub_hex = if i2o_ECPublicKey(eckey, ptr) == length
    buf.read_string(length).unpack("H*")[0]
  end

  EC_KEY_free(eckey)

  pub_hex
end