lib/bitcoin/ffi/openssl.rb in bitcoin-ruby-0.0.6 vs lib/bitcoin/ffi/openssl.rb in bitcoin-ruby-0.0.7

- old
+ new

@@ -1,11 +1,11 @@ # encoding: ascii-8bit # autoload when you need to re-generate a public_key from only its private_key. # ported from: https://github.com/sipa/bitcoin/blob/2d40fe4da9ea82af4b652b691a4185431d6e47a8/key.h -Bitcoin.require_dependency :ffi, exit: false, message: "Skipping FFI needed for OpenSSL_EC methods." +require 'ffi' module Bitcoin module OpenSSL_EC extend FFI::Library if FFI::Platform.windows? @@ -25,22 +25,24 @@ attach_function :BN_CTX_free, [:pointer], :int attach_function :BN_CTX_new, [], :pointer attach_function :BN_add, [:pointer, :pointer, :pointer], :int attach_function :BN_bin2bn, [:pointer, :int, :pointer], :pointer - attach_function :BN_bn2bin, [:pointer, :pointer], :void + attach_function :BN_bn2bin, [:pointer, :pointer], :int attach_function :BN_cmp, [:pointer, :pointer], :int attach_function :BN_copy, [:pointer, :pointer], :pointer attach_function :BN_dup, [:pointer], :pointer attach_function :BN_free, [:pointer], :int attach_function :BN_mod_inverse, [:pointer, :pointer, :pointer, :pointer], :pointer attach_function :BN_mod_mul, [:pointer, :pointer, :pointer, :pointer, :pointer], :int attach_function :BN_mod_sub, [:pointer, :pointer, :pointer, :pointer, :pointer], :int attach_function :BN_mul_word, [:pointer, :int], :int attach_function :BN_new, [], :pointer attach_function :BN_rshift, [:pointer, :pointer, :int], :int + attach_function :BN_rshift1, [:pointer, :pointer], :int attach_function :BN_set_word, [:pointer, :int], :int + attach_function :BN_sub, [:pointer, :pointer, :pointer], :int attach_function :EC_GROUP_get_curve_GFp, [:pointer, :pointer, :pointer, :pointer, :pointer], :int attach_function :EC_GROUP_get_degree, [:pointer], :int attach_function :EC_GROUP_get_order, [:pointer, :pointer, :pointer], :int attach_function :EC_KEY_free, [:pointer], :int attach_function :EC_KEY_get0_group, [:pointer], :pointer @@ -59,10 +61,17 @@ attach_function :i2o_ECPublicKey, [:pointer, :pointer], :uint attach_function :EC_KEY_check_key, [:pointer], :uint attach_function :ECDSA_do_sign, [:pointer, :uint, :pointer], :pointer attach_function :BN_num_bits, [:pointer], :int attach_function :ECDSA_SIG_free, [:pointer], :void + attach_function :EC_POINT_add, [:pointer, :pointer, :pointer, :pointer, :pointer], :int + attach_function :EC_POINT_point2hex, [:pointer, :pointer, :int, :pointer], :string + attach_function :EC_POINT_hex2point, [:pointer, :string, :pointer, :pointer], :pointer + attach_function :ECDSA_SIG_new, [], :pointer + attach_function :d2i_ECDSA_SIG, [:pointer, :pointer, :long], :pointer + attach_function :i2d_ECDSA_SIG, [:pointer, :pointer], :int + attach_function :OPENSSL_free, :CRYPTO_free, [:pointer], :void def self.BN_num_bytes(ptr); (BN_num_bits(ptr) + 7) / 8; end # resolve public from private key, using ffi and libssl.so @@ -221,10 +230,54 @@ EC_KEY_free(eckey) pub_hex end + # Regenerate a DER-encoded signature such that the S-value complies with the BIP62 + # specification. + # + def self.signature_to_low_s(signature) + init_ffi_ssl + + buf = FFI::MemoryPointer.new(:uint8, 34) + temp = signature.unpack("C*") + length_r = temp[3] + length_s = temp[5+length_r] + sig = FFI::MemoryPointer.from_string(signature) + + # Calculate the lower s value + s = BN_bin2bn(sig[6 + length_r], length_s, BN_new()) + eckey = EC_KEY_new_by_curve_name(NID_secp256k1) + group, order, halforder, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_new(), BN_CTX_new() + + EC_GROUP_get_order(group, order, ctx) + BN_rshift1(halforder, order) + if BN_cmp(s, halforder) > 0 + BN_sub(s, order, s) + end + + BN_free(halforder) + BN_free(order) + BN_CTX_free(ctx) + + length_s = BN_bn2bin(s, buf) + # p buf.read_string(length_s).unpack("H*") + + # Re-encode the signature in DER format + sig = [0x30, 0, 0x02, length_r] + sig.concat(temp.slice(4, length_r)) + sig << 0x02 + sig << length_s + sig.concat(buf.read_string(length_s).unpack("C*")) + sig[1] = sig.size - 2 + + BN_free(s) + EC_KEY_free(eckey) + + sig.pack("C*") + end + def self.sign_compact(hash, private_key, public_key_hex = nil, pubkey_compressed = nil) private_key = [private_key].pack("H*") if private_key.bytesize >= 64 private_key_hex = private_key.unpack("H*")[0] public_key_hex = regenerate_key(private_key_hex).last unless public_key_hex @@ -275,9 +328,52 @@ version = signature.unpack('C')[0] return false if version < 27 or version > 34 compressed = (version >= 31) ? (version -= 4; true) : false pubkey = recover_public_key_from_signature(hash, signature, version-27, compressed) + end + + # lifted from https://github.com/GemHQ/money-tree + def self.ec_add(point_0, point_1) + init_ffi_ssl + + eckey = EC_KEY_new_by_curve_name(NID_secp256k1) + group = EC_KEY_get0_group(eckey) + + point_0_hex = point_0.to_bn.to_s(16) + point_0_pt = EC_POINT_hex2point(group, point_0_hex, nil, nil) + point_1_hex = point_1.to_bn.to_s(16) + point_1_pt = EC_POINT_hex2point(group, point_1_hex, nil, nil) + + sum_point = EC_POINT_new(group) + success = EC_POINT_add(group, sum_point, point_0_pt, point_1_pt, nil) + hex = EC_POINT_point2hex(group, sum_point, POINT_CONVERSION_UNCOMPRESSED, nil) + EC_KEY_free(eckey) + EC_POINT_free(sum_point) + hex + end + + # repack signature for OpenSSL 1.0.1k handling of DER signatures + # https://github.com/bitcoin/bitcoin/pull/5634/files + def self.repack_der_signature(signature) + init_ffi_ssl + + return false if signature.empty? + + # New versions of OpenSSL will reject non-canonical DER signatures. de/re-serialize first. + norm_der = FFI::MemoryPointer.new(:pointer) + sig_ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, FFI::MemoryPointer.from_string(signature)) + + norm_sig = d2i_ECDSA_SIG(nil, sig_ptr, signature.bytesize) + + derlen = i2d_ECDSA_SIG(norm_sig, norm_der) + ECDSA_SIG_free(norm_sig) + return false if derlen <= 0 + + ret = norm_der.read_pointer.read_string(derlen) + OPENSSL_free(norm_der.read_pointer) + + ret end def self.init_ffi_ssl return if @ssl_loaded SSL_library_init()