lib/bitcoin/ffi/openssl.rb in bitcoin-ruby-0.0.18 vs lib/bitcoin/ffi/openssl.rb in bitcoin-ruby-0.0.19

- old
+ new

@@ -1,388 +1,405 @@ # 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 - require 'ffi' module Bitcoin -module OpenSSL_EC - extend FFI::Library - if FFI::Platform.windows? - ffi_lib 'libeay32', 'ssleay32' - else - ffi_lib [ 'libssl.so.1.0.0', 'ssl' ] - end + # 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 + module OpenSSL_EC # rubocop:disable Naming/ClassAndModuleCamelCase + extend FFI::Library + if FFI::Platform.windows? + ffi_lib 'libeay32', 'ssleay32' + else + ffi_lib ['libssl.so.1.0.0', 'ssl'] + end - NID_secp256k1 = 714 - POINT_CONVERSION_COMPRESSED = 2 - POINT_CONVERSION_UNCOMPRESSED = 4 + NID_secp256k1 = 714 # rubocop:disable Naming/ConstantName + POINT_CONVERSION_COMPRESSED = 2 + POINT_CONVERSION_UNCOMPRESSED = 4 - attach_function :SSL_library_init, [], :int - attach_function :ERR_load_crypto_strings, [], :void - attach_function :SSL_load_error_strings, [], :void - attach_function :RAND_poll, [], :int + attach_function :SSL_library_init, [], :int + attach_function :ERR_load_crypto_strings, [], :void + attach_function :SSL_load_error_strings, [], :void + attach_function :RAND_poll, [], :int - 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], :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 - attach_function :EC_KEY_get0_private_key, [:pointer], :pointer - attach_function :EC_KEY_new_by_curve_name, [:int], :pointer - attach_function :EC_KEY_set_conv_form, [:pointer, :int], :void - attach_function :EC_KEY_set_private_key, [:pointer, :pointer], :int - attach_function :EC_KEY_set_public_key, [:pointer, :pointer], :int - attach_function :EC_POINT_free, [:pointer], :int - attach_function :EC_POINT_is_at_infinity, [:pointer, :pointer], :int - attach_function :EC_POINT_mul, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int - attach_function :EC_POINT_new, [:pointer], :pointer - attach_function :EC_POINT_set_compressed_coordinates_GFp, [:pointer, :pointer, :pointer, :int, :pointer], :int - attach_function :d2i_ECPrivateKey, [:pointer, :pointer, :long], :pointer - attach_function :i2d_ECPrivateKey, [:pointer, :pointer], :int - 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 + attach_function :BN_CTX_free, [:pointer], :int + attach_function :BN_CTX_new, [], :pointer + attach_function :BN_add, %i[pointer pointer pointer], :int + attach_function :BN_bin2bn, %i[pointer int pointer], :pointer + attach_function :BN_bn2bin, %i[pointer pointer], :int + attach_function :BN_cmp, %i[pointer pointer], :int + attach_function :BN_copy, %i[pointer pointer], :pointer + attach_function :BN_dup, [:pointer], :pointer + attach_function :BN_free, [:pointer], :int + attach_function :BN_mod_inverse, %i[pointer pointer pointer pointer], :pointer + attach_function :BN_mod_mul, %i[pointer pointer pointer pointer pointer], :int + attach_function :BN_mod_sub, %i[pointer pointer pointer pointer pointer], :int + attach_function :BN_mul_word, %i[pointer int], :int + attach_function :BN_new, [], :pointer + attach_function :BN_rshift, %i[pointer pointer int], :int + attach_function :BN_rshift1, %i[pointer pointer], :int + attach_function :BN_set_word, %i[pointer int], :int + attach_function :BN_sub, %i[pointer pointer pointer], :int + attach_function :EC_GROUP_get_curve_GFp, %i[pointer pointer pointer pointer pointer], :int + attach_function :EC_GROUP_get_degree, [:pointer], :int + attach_function :EC_GROUP_get_order, %i[pointer pointer pointer], :int + attach_function :EC_KEY_free, [:pointer], :int + attach_function :EC_KEY_get0_group, [:pointer], :pointer + attach_function :EC_KEY_get0_private_key, [:pointer], :pointer + attach_function :EC_KEY_new_by_curve_name, [:int], :pointer + attach_function :EC_KEY_set_conv_form, %i[pointer int], :void + attach_function :EC_KEY_set_private_key, %i[pointer pointer], :int + attach_function :EC_KEY_set_public_key, %i[pointer pointer], :int + attach_function :EC_POINT_free, [:pointer], :int + attach_function :EC_POINT_is_at_infinity, %i[pointer pointer], :int + attach_function :EC_POINT_mul, %i[pointer pointer pointer pointer pointer pointer], :int + attach_function :EC_POINT_new, [:pointer], :pointer + attach_function :EC_POINT_set_compressed_coordinates_GFp, + %i[pointer pointer pointer int pointer], :int + attach_function :d2i_ECPrivateKey, %i[pointer pointer long], :pointer + attach_function :i2d_ECPrivateKey, %i[pointer pointer], :int + attach_function :i2o_ECPublicKey, %i[pointer pointer], :uint + attach_function :EC_KEY_check_key, [:pointer], :uint + attach_function :ECDSA_do_sign, %i[pointer uint pointer], :pointer + attach_function :BN_num_bits, [:pointer], :int + attach_function :ECDSA_SIG_free, [:pointer], :void + attach_function :EC_POINT_add, %i[pointer pointer pointer pointer pointer], :int + attach_function :EC_POINT_point2hex, %i[pointer pointer int pointer], :string + attach_function :EC_POINT_hex2point, %i[pointer string pointer pointer], :pointer + attach_function :ECDSA_SIG_new, [], :pointer + attach_function :d2i_ECDSA_SIG, %i[pointer pointer long], :pointer + attach_function :i2d_ECDSA_SIG, %i[pointer pointer], :int + attach_function :OPENSSL_free, :CRYPTO_free, [:pointer], :void - def self.BN_num_bytes(ptr); (BN_num_bits(ptr) + 7) / 8; end + def self.BN_num_bytes(ptr) # rubocop:disable Naming/MethodName + (BN_num_bits(ptr) + 7) / 8 + end + # resolve public from private key, using ffi and libssl.so + # example: + # keypair = Bitcoin.generate_key; Bitcoin::OpenSSL_EC.regenerate_key(keypair.first) == keypair + def self.regenerate_key(private_key) + private_key = [private_key].pack('H*') if private_key.bytesize >= (32 * 2) + private_key_hex = private_key.unpack('H*')[0] - # resolve public from private key, using ffi and libssl.so - # example: - # keypair = Bitcoin.generate_key; Bitcoin::OpenSSL_EC.regenerate_key(keypair.first) == keypair - def self.regenerate_key(private_key) - private_key = [private_key].pack("H*") if private_key.bytesize >= (32*2) - private_key_hex = private_key.unpack("H*")[0] + # private_key = FFI::MemoryPointer.new(:uint8, private_key.bytesize) + # .put_bytes(0, private_key, 0, private_key.bytesize) + private_key = FFI::MemoryPointer.from_string(private_key) - #private_key = FFI::MemoryPointer.new(:uint8, private_key.bytesize) - # .put_bytes(0, private_key, 0, private_key.bytesize) - private_key = FFI::MemoryPointer.from_string(private_key) + init_ffi_ssl + eckey = EC_KEY_new_by_curve_name(NID_secp256k1) + # priv_key = BN_bin2bn(private_key, private_key.size, BN_new()) + priv_key = BN_bin2bn(private_key, private_key.size - 1, BN_new()) - init_ffi_ssl - eckey = EC_KEY_new_by_curve_name(NID_secp256k1) - #priv_key = BN_bin2bn(private_key, private_key.size, BN_new()) - priv_key = BN_bin2bn(private_key, private_key.size-1, BN_new()) + group = EC_KEY_get0_group(eckey) + order = BN_new() + ctx = BN_CTX_new() + EC_GROUP_get_order(group, order, ctx) - 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) - 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) + BN_free(order) + BN_CTX_free(ctx) + EC_POINT_free(pub_key) + BN_free(priv_key) - BN_free(order) - BN_CTX_free(ctx) - EC_POINT_free(pub_key) - BN_free(priv_key) + length = i2d_ECPrivateKey(eckey, nil) + buf = FFI::MemoryPointer.new(:uint8, length) + ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf) + priv_hex = if i2d_ECPrivateKey(eckey, ptr) == length + size = buf.get_array_of_uint8(8, 1)[0] + buf.get_array_of_uint8(9, size).pack('C*').rjust(32, "\x00").unpack('H*')[0] + # der_to_private_key( ptr.read_pointer.read_string(length).unpack("H*")[0] ) + end + if priv_hex != private_key_hex + raise 'regenerated wrong private_key, raise here before generating a faulty public_key too!' + end - length = i2d_ECPrivateKey(eckey, nil) - buf = FFI::MemoryPointer.new(:uint8, length) - ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf) - priv_hex = if i2d_ECPrivateKey(eckey, ptr) == length - size = buf.get_array_of_uint8(8, 1)[0] - buf.get_array_of_uint8(9, size).pack("C*").rjust(32, "\x00").unpack("H*")[0] - #der_to_private_key( ptr.read_pointer.read_string(length).unpack("H*")[0] ) - end + length = i2o_ECPublicKey(eckey, nil) + buf = FFI::MemoryPointer.new(:uint8, length) + ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf) + pub_hex = buf.read_string(length).unpack('H*')[0] if i2o_ECPublicKey(eckey, ptr) == length - if priv_hex != private_key_hex - raise "regenerated wrong private_key, raise here before generating a faulty public_key too!" - end + EC_KEY_free(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] + [priv_hex, pub_hex] end - EC_KEY_free(eckey) + # extract private key from uncompressed DER format + def self.der_to_private_key(der_hex) + init_ffi_ssl + # k = EC_KEY_new_by_curve_name(NID_secp256k1) + # kp = FFI::MemoryPointer.new(:pointer).put_pointer(0, eckey) - [ priv_hex, pub_hex ] - end + buf = FFI::MemoryPointer.from_string([der_hex].pack('H*')) + ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf) - # extract private key from uncompressed DER format - def self.der_to_private_key(der_hex) - init_ffi_ssl - #k = EC_KEY_new_by_curve_name(NID_secp256k1) - #kp = FFI::MemoryPointer.new(:pointer).put_pointer(0, eckey) + # ec_key = d2i_ECPrivateKey(kp, ptr, buf.size-1) + ec_key = d2i_ECPrivateKey(nil, ptr, buf.size - 1) + return nil if ec_key.null? + bn = EC_KEY_get0_private_key(ec_key) + BN_bn2bin(bn, buf) + buf.read_string(32).unpack('H*')[0] + end - buf = FFI::MemoryPointer.from_string([der_hex].pack("H*")) - ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf) + # Given the components of a signature and a selector value, recover and + # return the public key that generated the signature according to the + # algorithm in SEC1v2 section 4.1.6. + # + # rec_id is an index from 0 to 3 that indicates which of the 4 possible + # keys is the correct one. Because the key recovery operation yields + # multiple potential keys, the correct key must either be stored alongside + # the signature, or you must be willing to try each rec_id in turn until + # you find one that outputs the key you are expecting. + # + # If this method returns nil, it means recovery was not possible and rec_id + # should be iterated. + # + # Given the above two points, a correct usage of this method is inside a + # for loop from 0 to 3, and if the output is nil OR a key that is not the + # one you expect, you try again with the next rec_id. + # + # message_hash = hash of the signed message. + # signature = the R and S components of the signature, wrapped. + # rec_id = which possible key to recover. + # is_compressed = whether or not the original pubkey was compressed. + def self.recover_public_key_from_signature(message_hash, signature, rec_id, is_compressed) + return nil if rec_id < 0 || signature.bytesize != 65 + init_ffi_ssl - #ec_key = d2i_ECPrivateKey(kp, ptr, buf.size-1) - ec_key = d2i_ECPrivateKey(nil, ptr, buf.size-1) - return nil if ec_key.null? - bn = EC_KEY_get0_private_key(ec_key) - BN_bn2bin(bn, buf) - buf.read_string(32).unpack("H*")[0] - end + signature = FFI::MemoryPointer.from_string(signature) + # signature_bn = BN_bin2bn(signature, 65, BN_new()) + r = BN_bin2bn(signature[1], 32, BN_new()) + s = BN_bin2bn(signature[33], 32, BN_new()) - # Given the components of a signature and a selector value, recover and - # return the public key that generated the signature according to the - # algorithm in SEC1v2 section 4.1.6. - # - # rec_id is an index from 0 to 3 that indicates which of the 4 possible - # keys is the correct one. Because the key recovery operation yields - # multiple potential keys, the correct key must either be stored alongside - # the signature, or you must be willing to try each rec_id in turn until - # you find one that outputs the key you are expecting. - # - # If this method returns nil, it means recovery was not possible and rec_id - # should be iterated. - # - # Given the above two points, a correct usage of this method is inside a - # for loop from 0 to 3, and if the output is nil OR a key that is not the - # one you expect, you try again with the next rec_id. - # - # message_hash = hash of the signed message. - # signature = the R and S components of the signature, wrapped. - # rec_id = which possible key to recover. - # is_compressed = whether or not the original pubkey was compressed. - def self.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 + i = rec_id / 2 + eckey = EC_KEY_new_by_curve_name(NID_secp256k1) - signature = FFI::MemoryPointer.from_string(signature) - #signature_bn = BN_bin2bn(signature, 65, BN_new()) - r = BN_bin2bn(signature[1], 32, BN_new()) - s = BN_bin2bn(signature[33], 32, BN_new()) + EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED) if is_compressed - n, i = 0, rec_id / 2 - eckey = EC_KEY_new_by_curve_name(NID_secp256k1) + 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) - EC_KEY_set_conv_form(eckey, POINT_CONVERSION_COMPRESSED) if is_compressed + field = BN_new() + EC_GROUP_get_curve_GFp(group, field, nil, nil, nil) - 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) + if BN_cmp(x, field) >= 0 + [r, s, order, x, field].each { |item| BN_free(item) } + EC_KEY_free(eckey) + return nil + end - field = BN_new() - EC_GROUP_get_curve_GFp(group, field, nil, nil, nil) + big_r = EC_POINT_new(group) + EC_POINT_set_compressed_coordinates_GFp(group, big_r, x, rec_id % 2, nil) - if BN_cmp(x, field) >= 0 - [r, s, order, x, field].each{|i| BN_free(i) } - EC_KEY_free(eckey) - return nil - end + 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 - big_r = EC_POINT_new(group) - EC_POINT_set_compressed_coordinates_GFp(group, big_r, x, rec_id % 2, nil) + ctx = BN_CTX_new() + zero = BN_new() + rr = BN_new() + sor = BN_new() + eor = 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) - 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 + [r, s, order, x, field, e, zero, rr, sor, eor].each { |item| BN_free(item) } + [big_r, big_q].each { |item| EC_POINT_free(item) } - 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) + length = i2o_ECPublicKey(eckey, nil) + buf = FFI::MemoryPointer.new(:uint8, length) + ptr = FFI::MemoryPointer.new(:pointer).put_pointer(0, buf) + pub_hex = buf.read_string(length).unpack('H*')[0] if i2o_ECPublicKey(eckey, ptr) == length - [r, s, order, x, field, e, zero, rr, sor, eor].each{|i| BN_free(i) } - [big_r, big_q].each{|i| EC_POINT_free(i) } + EC_KEY_free(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] + pub_hex end - EC_KEY_free(eckey) + # 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 - pub_hex - end + 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) - # 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 + # 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 = EC_KEY_get0_group(eckey) + order = BN_new() + halforder = BN_new() + ctx = BN_CTX_new() - 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) + EC_GROUP_get_order(group, order, ctx) + BN_rshift1(halforder, order) + BN_sub(s, order, s) if BN_cmp(s, halforder) > 0 - # 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() + BN_free(halforder) + BN_free(order) + BN_CTX_free(ctx) - EC_GROUP_get_order(group, order, ctx) - BN_rshift1(halforder, order) - if BN_cmp(s, halforder) > 0 - BN_sub(s, order, s) - end + length_s = BN_bn2bin(s, buf) + # p buf.read_string(length_s).unpack("H*") - BN_free(halforder) - BN_free(order) - BN_CTX_free(ctx) + # 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 - length_s = BN_bn2bin(s, buf) - # p buf.read_string(length_s).unpack("H*") + BN_free(s) + EC_KEY_free(eckey) - # 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 + sig.pack('C*') + end - BN_free(s) - EC_KEY_free(eckey) + def self.sign_compact(hash, private_key, public_key_hex = nil, pubkey_compressed = nil) + msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hash) - sig.pack("C*") - end + private_key = [private_key].pack('H*') if private_key.bytesize >= 64 + private_key_hex = private_key.unpack('H*')[0] - def self.sign_compact(hash, private_key, public_key_hex = nil, pubkey_compressed = nil) - msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hash) + public_key_hex ||= regenerate_key(private_key_hex).last + pubkey_compressed ||= public_key_hex[0..1] != '04' - private_key = [private_key].pack("H*") if private_key.bytesize >= 64 - private_key_hex = private_key.unpack("H*")[0] + init_ffi_ssl + eckey = EC_KEY_new_by_curve_name(NID_secp256k1) + priv_key = BN_bin2bn(private_key, private_key.bytesize, BN_new()) - public_key_hex = regenerate_key(private_key_hex).last unless public_key_hex - pubkey_compressed = (public_key_hex[0..1] == "04" ? false : true) unless pubkey_compressed + group = EC_KEY_get0_group(eckey) + order = BN_new() + ctx = BN_CTX_new() + EC_GROUP_get_order(group, order, ctx) - init_ffi_ssl - eckey = EC_KEY_new_by_curve_name(NID_secp256k1) - priv_key = BN_bin2bn(private_key, private_key.bytesize, BN_new()) + 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) - group, order, ctx = EC_KEY_get0_group(eckey), BN_new(), BN_CTX_new() - EC_GROUP_get_order(group, order, ctx) + signature = ECDSA_do_sign(msg32, msg32.size, eckey) - 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) + BN_free(order) + BN_CTX_free(ctx) + EC_POINT_free(pub_key) + BN_free(priv_key) + EC_KEY_free(eckey) - signature = ECDSA_do_sign(msg32, msg32.size, eckey) + buf = FFI::MemoryPointer.new(:uint8, 32) + head = nil + r, s = signature.get_array_of_pointer(0, 2).map do |i| + BN_bn2bin(i, buf) + buf.read_string(BN_num_bytes(i)).rjust(32, "\x00") + end - BN_free(order) - BN_CTX_free(ctx) - EC_POINT_free(pub_key) - BN_free(priv_key) - EC_KEY_free(eckey) + rec_id = nil + if signature.get_array_of_pointer(0, 2).all? { |i| BN_num_bits(i) <= 256 } + 4.times do |i| + head = [27 + i + (pubkey_compressed ? 4 : 0)].pack('C') + recovered_key = recover_public_key_from_signature( + msg32.read_string(32), [head, r, s].join, i, pubkey_compressed + ) + if public_key_hex == recovered_key + rec_id = i + break + end + end + end - 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") } + ECDSA_SIG_free(signature) - if signature.get_array_of_pointer(0, 2).all?{|i| BN_num_bits(i) <= 256 } - 4.times{|i| - head = [ 27 + i + (pubkey_compressed ? 4 : 0) ].pack("C") - if public_key_hex == recover_public_key_from_signature(msg32.read_string(32), [head, r, s].join, i, pubkey_compressed) - rec_id = i; break - end - } + [head, [r, s]].join if rec_id end - ECDSA_SIG_free(signature) + def self.recover_compact(hash, signature) + return false if signature.bytesize != 65 + msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hash) - [ head, [r,s] ].join if rec_id - end + version = signature.unpack('C')[0] + return false if version < 27 || version > 34 - def self.recover_compact(hash, signature) - return false if signature.bytesize != 65 - msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hash) + compressed = version >= 31 + version -= 4 if compressed - version = signature.unpack('C')[0] - return false if version < 27 or version > 34 + recover_public_key_from_signature(msg32.read_string(32), signature, version - 27, compressed) + end - compressed = (version >= 31) ? (version -= 4; true) : false - pubkey = recover_public_key_from_signature(msg32.read_string(32), signature, version-27, compressed) - end + # lifted from https://github.com/GemHQ/money-tree + def self.ec_add(point0, point1) + init_ffi_ssl - # 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) - eckey = EC_KEY_new_by_curve_name(NID_secp256k1) - group = EC_KEY_get0_group(eckey) + point_0_hex = point0.to_bn.to_s(16) + point_0_pt = EC_POINT_hex2point(group, point_0_hex, nil, nil) + point_1_hex = point1.to_bn.to_s(16) + point_1_pt = EC_POINT_hex2point(group, point_1_hex, nil, nil) - 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) + 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 - 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 - # 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? - 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) + ) - # 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) - 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 - 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 = norm_der.read_pointer.read_string(derlen) - OPENSSL_free(norm_der.read_pointer) + ret + end - ret + def self.init_ffi_ssl + @ssl_loaded ||= false + return if @ssl_loaded + SSL_library_init() + ERR_load_crypto_strings() + SSL_load_error_strings() + RAND_poll() + @ssl_loaded = true + end end - - def self.init_ffi_ssl - return if @ssl_loaded - SSL_library_init() - ERR_load_crypto_strings() - SSL_load_error_strings() - RAND_poll() - @ssl_loaded = true - end -end end