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

- old
+ new

@@ -1,21 +1,20 @@ # encoding: ascii-8bit -# bindings for secp256k1 inside bitcoin (https://github.com/bitcoin/bitcoin/tree/v0.13.1/src/secp256k1) -# tag: v0.13.1 -# commit: 03422e564b552c1d3c16ae854f8471f7cb39e25d -# bitcoin@master% git checkout v0.13.1 -# bitcoin@tags/v0.13.1^0% cd src/secp256k1 -# bitcoin@tags/v0.13.1^0 src/secp256k1% ./autogen.sh -# bitcoin@tags/v0.13.1^0 src/secp256k1% ./configure --enable-module-recovery -# bitcoin@tags/v0.13.1^0 src/secp256k1% make libsecp256k1.la -# bitcoin@tags/v0.13.1^0 src/secp256k1% nm -D .libs/libsecp256k1.so.0.0.0 | grep secp -# export SECP256K1_LIB_PATH=/path/to/bitcoin/src/secp256k1/.libs/libsecp256k1.so.0.0.0 - require 'ffi' module Bitcoin + # bindings for secp256k1 inside bitcoin (https://github.com/bitcoin/bitcoin/tree/v0.13.1/src/secp256k1) + # tag: v0.13.1 + # commit: 03422e564b552c1d3c16ae854f8471f7cb39e25d + # bitcoin@master% git checkout v0.13.1 + # bitcoin@tags/v0.13.1^0% cd src/secp256k1 + # bitcoin@tags/v0.13.1^0 src/secp256k1% ./autogen.sh + # bitcoin@tags/v0.13.1^0 src/secp256k1% ./configure --enable-module-recovery + # bitcoin@tags/v0.13.1^0 src/secp256k1% make libsecp256k1.la + # bitcoin@tags/v0.13.1^0 src/secp256k1% nm -D .libs/libsecp256k1.so.0.0.0 | grep secp + # export SECP256K1_LIB_PATH=/path/to/bitcoin/src/secp256k1/.libs/libsecp256k1.so.0.0.0 module Secp256k1 extend FFI::Library SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1) SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0) @@ -30,14 +29,14 @@ SECP256K1_CONTEXT_VERIFY = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY) SECP256K1_CONTEXT_SIGN = (SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN) # Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export. SECP256K1_EC_COMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION) - SECP256K1_EC_UNCOMPRESSED = (SECP256K1_FLAGS_TYPE_COMPRESSION) + SECP256K1_EC_UNCOMPRESSED = SECP256K1_FLAGS_TYPE_COMPRESSION def self.ffi_load_functions(file) - class_eval <<-RUBY + class_eval <<-RUBY # rubocop:disable Style/EvalWithLocation ffi_lib [ %[#{file}] ] ## # source: https://github.com/bitcoin/bitcoin/blob/v0.13.1/src/secp256k1/include/secp256k1.h ## @@ -89,178 +88,212 @@ # int secp256k1_ecdsa_signature_parse_der(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen) attach_function :secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int # TODO: add or port - # # int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen) + # # int ecdsa_signature_parse_der_lax(const secp256k1_context* ctx, secp256k1_ecdsa_signature* sig, const unsigned char *input, size_t inputlen) # attach_function :ecdsa_signature_parse_der_lax, [:pointer, :pointer, :pointer, :size_t], :int RUBY end def self.init + @loaded ||= false return if @loaded - lib_path = [ ENV['SECP256K1_LIB_PATH'], 'vendor/bitcoin/src/secp256k1/.libs/libsecp256k1.so' ].find{|f| File.exists?(f.to_s) } + lib_path = [ + ENV['SECP256K1_LIB_PATH'], 'vendor/bitcoin/src/secp256k1/.libs/libsecp256k1.so' + ].find { |f| File.exist?(f.to_s) } ffi_load_functions(lib_path) @loaded = true end - def self.with_context(flags=nil, seed=nil) + def self.with_context(flags = nil, seed = nil) init - flags = flags || (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN) + flags ||= (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN) context = secp256k1_context_create(flags) - ret, tries, max = 0, 0, 20 + ret = 0 + tries = 0 + max = 20 while ret != 1 - raise "secp256k1_context_randomize failed." if tries >= max + raise 'secp256k1_context_randomize failed.' if tries >= max tries += 1 - ret = secp256k1_context_randomize(context, FFI::MemoryPointer.from_string(seed || SecureRandom.random_bytes(32))) + ret = secp256k1_context_randomize( + context, FFI::MemoryPointer.from_string(seed || SecureRandom.random_bytes(32)) + ) end yield(context) if block_given? ensure secp256k1_context_destroy(context) end - def self.generate_key_pair(compressed=true) + def self.generate_key_pair(compressed = true) with_context do |context| - - ret, tries, max = 0, 0, 20 + ret = 0 + tries = 0 + max = 20 while ret != 1 - raise "secp256k1_ec_seckey_verify in generate_key_pair failed." if tries >= max + raise 'secp256k1_ec_seckey_verify in generate_key_pair failed.' if tries >= max tries += 1 seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32)) ret = secp256k1_ec_seckey_verify(context, seckey) end internal_pubkey = FFI::MemoryPointer.new(:uchar, 64) result = secp256k1_ec_pubkey_create(context, internal_pubkey, seckey) - raise "error creating pubkey" unless result == 1 + raise 'error creating pubkey' unless result == 1 - pubkey, pubkey_len = FFI::MemoryPointer.new(:uchar, 65), FFI::MemoryPointer.new(:uint64) + pubkey = FFI::MemoryPointer.new(:uchar, 65) + pubkey_len = FFI::MemoryPointer.new(:uint64) result = if compressed - pubkey_len.put_uint64(0, 33) - secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED) - else - pubkey_len.put_uint64(0, 65) - secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED) - end - raise "error serialize pubkey" unless (result == 1) || pubkey_len.read_uint64 > 0 + pubkey_len.put_uint64(0, 33) + secp256k1_ec_pubkey_serialize( + context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_COMPRESSED + ) + else + pubkey_len.put_uint64(0, 65) + secp256k1_ec_pubkey_serialize( + context, pubkey, pubkey_len, internal_pubkey, SECP256K1_EC_UNCOMPRESSED + ) + end + raise 'error serialize pubkey' unless (result == 1) || pubkey_len.read_uint64 > 0 - [ seckey.read_string(32), pubkey.read_string(pubkey_len.read_uint64) ] + [seckey.read_string(32), pubkey.read_string(pubkey_len.read_uint64)] end end - def self.generate_key(compressed=true) + def self.generate_key(compressed = true) priv, pub = generate_key_pair(compressed) - Bitcoin::Key.new(priv.unpack("H*")[0], pub.unpack("H*")[0]) + Bitcoin::Key.new(priv.unpack('H*')[0], pub.unpack('H*')[0]) end def self.sign(data, priv_key) with_context do |context| seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key) - raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey) == 1 + raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, seckey) == 1 internal_signature = FFI::MemoryPointer.new(:uchar, 64) msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data) - ret, tries, max = 0, 0, 20 + ret = 0 + tries = 0 + max = 20 while ret != 1 - raise "secp256k1_ecdsa_sign failed." if tries >= max + raise 'secp256k1_ecdsa_sign failed.' if tries >= max tries += 1 ret = secp256k1_ecdsa_sign(context, internal_signature, msg32, seckey, nil, nil) end - signature, signature_len = FFI::MemoryPointer.new(:uchar, 72), FFI::MemoryPointer.new(:uint64).put_uint64(0, 72) - result = secp256k1_ecdsa_signature_serialize_der(context, signature, signature_len, internal_signature) - raise "secp256k1_ecdsa_signature_serialize_der failed" unless result == 1 + signature = FFI::MemoryPointer.new(:uchar, 72) + signature_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 72) + result = secp256k1_ecdsa_signature_serialize_der( + context, signature, signature_len, internal_signature + ) + raise 'secp256k1_ecdsa_signature_serialize_der failed' unless result == 1 signature.read_string(signature_len.read_uint64) end end def self.verify(data, sig, pub_key) with_context do |context| - return false if data.bytesize == 0 + return false if data.bytesize.zero? pubkey = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key) internal_pubkey = FFI::MemoryPointer.new(:uchar, 64) result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size) return false unless result == 1 signature = FFI::MemoryPointer.new(:uchar, sig.bytesize).put_bytes(0, sig) internal_signature = FFI::MemoryPointer.new(:uchar, 64) - result = secp256k1_ecdsa_signature_parse_der(context, internal_signature, signature, signature.size) - #result = ecdsa_signature_parse_der_lax(context, internal_signature, signature, signature.size) + result = secp256k1_ecdsa_signature_parse_der( + context, internal_signature, signature, signature.size + ) + # result = ecdsa_signature_parse_der_lax( + # context, internal_signature, signature, signature.size + # ) return false unless result == 1 - # libsecp256k1's ECDSA verification requires lower-S signatures, which have not historically been enforced in Bitcoin, so normalize them first. + # libsecp256k1's ECDSA verification requires lower-S signatures, which have not historically + # been enforced in Bitcoin, so normalize them first. secp256k1_ecdsa_signature_normalize(context, internal_signature, internal_signature) msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data) result = secp256k1_ecdsa_verify(context, internal_signature, msg32, internal_pubkey) return result == 1 end end - def self.sign_compact(message, priv_key, compressed=true) + def self.sign_compact(message, priv_key, compressed = true) with_context do |context| seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key) - raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey) == 1 + raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, seckey) == 1 msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message) internal_recoverable_signature = FFI::MemoryPointer.new(:uchar, 65) rec_id = FFI::MemoryPointer.new(:int).put_int(0, -1) - ret, tries, max = 0, 0, 20 + ret = 0 + tries = 0 + max = 20 while ret != 1 - raise "secp256k1_ecdsa_sign_recoverable failed." if tries >= max + raise 'secp256k1_ecdsa_sign_recoverable failed.' if tries >= max tries += 1 - ret = secp256k1_ecdsa_sign_recoverable(context, internal_recoverable_signature, msg32, seckey, nil, nil) + ret = secp256k1_ecdsa_sign_recoverable( + context, internal_recoverable_signature, msg32, seckey, nil, nil + ) end recoverable_signature = FFI::MemoryPointer.new(:uchar, 64) - result = secp256k1_ecdsa_recoverable_signature_serialize_compact(context, recoverable_signature, rec_id, internal_recoverable_signature) - raise "secp256k1_ecdsa_recoverable_signature_serialize_compact failed" unless result == 1 - raise "secp256k1_ecdsa_recoverable_signature_serialize_compact failed" unless rec_id.read_int != -1 + result = secp256k1_ecdsa_recoverable_signature_serialize_compact( + context, recoverable_signature, rec_id, internal_recoverable_signature + ) + error_str = 'secp256k1_ecdsa_recoverable_signature_serialize_compact failed' + raise error_str unless result == 1 + raise error_str unless rec_id.read_int != -1 - header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack("C") - [ header, recoverable_signature.read_string(64) ].join + header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack('C') + [header, recoverable_signature.read_string(64)].join end end def self.recover_compact(message, signature) with_context do |context| return nil if signature.bytesize != 65 version = signature.unpack('C')[0] return nil if version < 27 || version > 34 - compressed = version >= 31 ? true : false + compressed = version >= 31 flag = compressed ? SECP256K1_EC_COMPRESSED : SECP256K1_EC_UNCOMPRESSED version -= 4 if compressed recid = version - 27 msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message) recoverable_signature = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1]) internal_recoverable_signature = FFI::MemoryPointer.new(:uchar, 65) - result = secp256k1_ecdsa_recoverable_signature_parse_compact(context, internal_recoverable_signature, recoverable_signature, recid) + result = secp256k1_ecdsa_recoverable_signature_parse_compact( + context, internal_recoverable_signature, recoverable_signature, recid + ) return nil unless result == 1 internal_pubkey = FFI::MemoryPointer.new(:uchar, 64) - result = secp256k1_ecdsa_recover(context, internal_pubkey, internal_recoverable_signature, msg32) + result = secp256k1_ecdsa_recover( + context, internal_pubkey, internal_recoverable_signature, msg32 + ) return nil unless result == 1 - pubkey, pubkey_len = FFI::MemoryPointer.new(:uchar, 65), FFI::MemoryPointer.new(:uint64).put_uint64(0, 65) + pubkey = FFI::MemoryPointer.new(:uchar, 65) + pubkey_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 65) result = secp256k1_ec_pubkey_serialize(context, pubkey, pubkey_len, internal_pubkey, flag) - raise "error serialize pubkey" unless (result == 1) || pubkey_len.read_uint64 > 0 + raise 'error serialize pubkey' unless (result == 1) || pubkey_len.read_uint64 > 0 pubkey.read_string(pubkey_len.read_uint64) end end - end end