lib/bitcoin/ffi/secp256k1.rb in bitcoin-ruby-0.0.9 vs lib/bitcoin/ffi/secp256k1.rb in bitcoin-ruby-0.0.10

- old
+ new

@@ -1,81 +1,100 @@ # encoding: ascii-8bit -# bindings for secp256k1 inside bitcoin (https://github.com/bitcoin/bitcoin/tree/v0.11.0/src/secp256k1) -# tag: v0.11.0 -# commit: d26f951802c762de04fb68e1a112d611929920ba +# 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 module Secp256k1 extend FFI::Library - SECP256K1_START_VERIFY = (1 << 0) - SECP256K1_START_SIGN = (1 << 1) + SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1) + SECP256K1_FLAGS_TYPE_CONTEXT = (1 << 0) + SECP256K1_FLAGS_TYPE_COMPRESSION = (1 << 1) + # The higher bits contain the actual data. Do not use directly. + SECP256K1_FLAGS_BIT_CONTEXT_VERIFY = (1 << 8) + SECP256K1_FLAGS_BIT_CONTEXT_SIGN = (1 << 9) + SECP256K1_FLAGS_BIT_COMPRESSION = (1 << 8) + + # Flags to pass to secp256k1_context_create. + 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) + def self.ffi_load_functions(file) class_eval <<-RUBY ffi_lib [ %[#{file}] ] ## - # source: https://github.com/bitcoin/bitcoin/blob/v0.11.0/src/secp256k1/include/secp256k1.h + # source: https://github.com/bitcoin/bitcoin/blob/v0.13.1/src/secp256k1/include/secp256k1.h ## - # secp256k1_context_t* secp256k1_context_create(int flags) - attach_function :secp256k1_context_create, [:int], :pointer + # secp256k1_context* secp256k1_context_create(unsigned int flags) + attach_function :secp256k1_context_create, [:uint], :pointer - # secp256k1_context_t* secp256k1_context_clone(const secp256k1_context_t* ctx) - attach_function :secp256k1_context_clone, [:pointer], :pointer - - # void secp256k1_context_destroy(secp256k1_context_t* ctx) + # void secp256k1_context_destroy(secp256k1_context* ctx) attach_function :secp256k1_context_destroy, [:pointer], :void - # int secp256k1_ecdsa_verify(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sig, int siglen, const unsigned char *pubkey, int pubkeylen) - attach_function :secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :int, :pointer, :int], :int + # int secp256k1_context_randomize(secp256k1_context* ctx, const unsigned char *seed32) + attach_function :secp256k1_context_randomize, [:pointer, :pointer], :int - # int secp256k1_ecdsa_sign(const secp256k1_context_t* ctx, const unsigned char *msg32, unsigned char *sig, int *siglen, const unsigned char *seckey, secp256k1_nonce_function_t noncefp, const void *ndata) - attach_function :secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int + # int secp256k1_ec_seckey_verify(const secp256k1_context* ctx, const unsigned char *seckey) + attach_function :secp256k1_ec_seckey_verify, [:pointer, :pointer], :int - # int secp256k1_ecdsa_sign_compact(const secp256k1_context_t* ctx, const unsigned char *msg32, unsigned char *sig64, const unsigned char *seckey, secp256k1_nonce_function_t noncefp, const void *ndata, int *recid) - attach_function :secp256k1_ecdsa_sign_compact, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int + # int secp256k1_ec_pubkey_create(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const unsigned char *seckey) + attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int - # int secp256k1_ecdsa_recover_compact(const secp256k1_context_t* ctx, const unsigned char *msg32, const unsigned char *sig64, unsigned char *pubkey, int *pubkeylen, int compressed, int recid) - attach_function :secp256k1_ecdsa_recover_compact, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :int], :int + # int secp256k1_ec_pubkey_serialize(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_pubkey* pubkey, unsigned int flags) + attach_function :secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int - # int secp256k1_ec_seckey_verify(const secp256k1_context_t* ctx, const unsigned char *seckey) - attach_function :secp256k1_ec_seckey_verify, [:pointer, :pointer], :int + # int secp256k1_ecdsa_sign_recoverable(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature *sig, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void *ndata) + attach_function :secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int - # int secp256k1_ec_pubkey_verify(const secp256k1_context_t* ctx, const unsigned char *pubkey, int pubkeylen) - attach_function :secp256k1_ec_pubkey_verify, [:pointer, :pointer, :int], :int + # int secp256k1_ecdsa_recoverable_signature_serialize_compact(const secp256k1_context* ctx, unsigned char *output64, int *recid, const secp256k1_ecdsa_recoverable_signature* sig) + attach_function :secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int - # int secp256k1_ec_pubkey_create(const secp256k1_context_t* ctx, unsigned char *pubkey, int *pubkeylen, const unsigned char *seckey, int compressed) - attach_function :secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer, :pointer, :int], :int + # int secp256k1_ecdsa_recoverable_signature_parse_compact(const secp256k1_context* ctx, secp256k1_ecdsa_recoverable_signature* sig, const unsigned char *input64, int recid) + attach_function :secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int - # int secp256k1_ec_pubkey_decompress(const secp256k1_context_t* ctx, unsigned char *pubkey, int *pubkeylen) - attach_function :secp256k1_ec_pubkey_decompress, [:pointer, :pointer, :pointer], :int + # int secp256k1_ecdsa_recover(const secp256k1_context* ctx, secp256k1_pubkey *pubkey, const secp256k1_ecdsa_recoverable_signature *sig, const unsigned char *msg32) + attach_function :secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int - # int secp256k1_ec_privkey_export(const secp256k1_context_t* ctx, const unsigned char *seckey, unsigned char *privkey, int *privkeylen, int compressed) - attach_function :secp256k1_ec_privkey_export, [:pointer, :pointer, :pointer, :pointer, :int], :int + # int secp256k1_ecdsa_sign(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const unsigned char *seckey, secp256k1_nonce_function noncefp, const void *ndata) + attach_function :secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int - # int secp256k1_ec_privkey_import(const secp256k1_context_t* ctx, unsigned char *seckey, const unsigned char *privkey, int privkeylen) - attach_function :secp256k1_ec_privkey_import, [:pointer, :pointer, :pointer, :pointer], :int + # int secp256k1_ecdsa_signature_serialize_der(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_ecdsa_signature* sig) + attach_function :secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int - # int secp256k1_ec_privkey_tweak_add(const secp256k1_context_t* ctx, unsigned char *seckey, const unsigned char *tweak) - attach_function :secp256k1_ec_privkey_tweak_add, [:pointer, :pointer, :pointer], :int + # int secp256k1_ec_pubkey_parse(const secp256k1_context* ctx, secp256k1_pubkey* pubkey, const unsigned char *input, size_t inputlen) + attach_function :secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int - # int secp256k1_ec_pubkey_tweak_add(const secp256k1_context_t* ctx, unsigned char *pubkey, int pubkeylen, const unsigned char *tweak) - attach_function :secp256k1_ec_pubkey_tweak_add, [:pointer, :pointer, :int, :pointer], :int + # int secp256k1_ecdsa_signature_normalize(const secp256k1_context* ctx, secp256k1_ecdsa_signature *sigout, const secp256k1_ecdsa_signature *sigin) + attach_function :secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int - # int secp256k1_ec_privkey_tweak_mul(const secp256k1_context_t* ctx, unsigned char *seckey, const unsigned char *tweak) - attach_function :secp256k1_ec_privkey_tweak_mul, [:pointer, :pointer, :pointer], :int + # int secp256k1_ecdsa_verify(const secp256k1_context* ctx, const secp256k1_ecdsa_signature *sig, const unsigned char *msg32, const secp256k1_pubkey *pubkey) + attach_function :secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int - # int secp256k1_ec_pubkey_tweak_mul(const secp256k1_context_t* ctx, unsigned char *pubkey, int pubkeylen, const unsigned char *tweak) - attach_function :secp256k1_ec_pubkey_tweak_mul, [:pointer, :pointer, :int, :pointer], :int + # 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 - # int secp256k1_context_randomize(secp256k1_context_t* ctx, const unsigned char *seed32) - attach_function :secp256k1_context_randomize, [:pointer, :pointer], :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) + # attach_function :ecdsa_signature_parse_der_lax, [:pointer, :pointer, :pointer, :size_t], :int RUBY end def self.init return if @loaded @@ -84,11 +103,11 @@ @loaded = true end def self.with_context(flags=nil, seed=nil) init - flags = flags || (SECP256K1_START_VERIFY | SECP256K1_START_SIGN ) + flags = flags || (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN) context = secp256k1_context_create(flags) ret, tries, max = 0, 0, 20 while ret != 1 raise "secp256k1_context_randomize failed." if tries >= max @@ -107,76 +126,110 @@ ret, tries, max = 0, 0, 20 while ret != 1 raise "secp256k1_ec_seckey_verify in generate_key_pair failed." if tries >= max tries += 1 - priv_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32)) - ret = secp256k1_ec_seckey_verify(context, priv_key) + seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32)) + ret = secp256k1_ec_seckey_verify(context, seckey) end - pub_key, pub_key_length = FFI::MemoryPointer.new(:uchar, 65), FFI::MemoryPointer.new(:int) - result = secp256k1_ec_pubkey_create(context, pub_key, pub_key_length, priv_key, compressed ? 1 : 0) + internal_pubkey = FFI::MemoryPointer.new(:uchar, 64) + result = secp256k1_ec_pubkey_create(context, internal_pubkey, seckey) raise "error creating pubkey" unless result - [ priv_key.read_string(32), pub_key.read_string(pub_key_length.read_int) ] + pubkey, pubkey_len = FFI::MemoryPointer.new(:uchar, 65), 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 || pubkey_len.read_uint64 > 0 + + [ seckey.read_string(32), pubkey.read_string(pubkey_len.read_uint64) ] end end def self.generate_key(compressed=true) priv, pub = generate_key_pair(compressed) Bitcoin::Key.new(priv.unpack("H*")[0], pub.unpack("H*")[0]) end def self.sign(data, priv_key) with_context do |context| - msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data) seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key) raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey) - sig, siglen = FFI::MemoryPointer.new(:uchar, 72), FFI::MemoryPointer.new(:int).write_int(72) + internal_signature = FFI::MemoryPointer.new(:uchar, 64) + msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data) - while true do - break if secp256k1_ecdsa_sign(context, msg32, sig, siglen, seckey, nil, nil) + ret, tries, max = 0, 0, 20 + while ret != 1 + raise "secp256k1_ecdsa_sign failed." if tries >= max + tries += 1 + + ret = secp256k1_ecdsa_sign(context, internal_signature, msg32, seckey, nil, nil) end - sig.read_string(siglen.read_int) + 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 + + signature.read_string(signature_len.read_uint64) end end - def self.verify(data, signature, pub_key) + def self.verify(data, sig, pub_key) with_context do |context| - data_buf = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data) - sig_buf = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature) - pub_buf = FFI::MemoryPointer.new(:uchar, pub_key.bytesize).put_bytes(0, pub_key) + return false if data.bytesize == 0 - result = secp256k1_ecdsa_verify(context, data_buf, sig_buf, sig_buf.size, pub_buf, pub_buf.size) + 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 - case result - when 0; false - when 1; true - when -1; raise "error invalid pubkey" - when -2; raise "error invalid signature" - else ; raise "error invalid result" - end + 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) + return false unless result + + # 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 ? true : false end end def self.sign_compact(message, priv_key, compressed=true) with_context do |context| - msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, message) - sig64 = FFI::MemoryPointer.new(:uchar, 64) - rec_id = FFI::MemoryPointer.new(:int) - seckey = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key) raise "priv_key invalid" unless secp256k1_ec_seckey_verify(context, seckey) - while true do - break if secp256k1_ecdsa_sign_compact(context, msg32, sig64, seckey, nil, nil, rec_id) + 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 + while ret != 1 + raise "secp256k1_ecdsa_sign_recoverable failed." if tries >= max + tries += 1 + + 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 + raise "secp256k1_ecdsa_recoverable_signature_serialize_compact failed" unless rec_id.read_int != -1 + header = [27 + rec_id.read_int + (compressed ? 4 : 0)].pack("C") - [ header, sig64.read_string(64) ].join + [ header, recoverable_signature.read_string(64) ].join end end def self.recover_compact(message, signature) with_context do |context| @@ -184,22 +237,29 @@ version = signature.unpack('C')[0] return nil if version < 27 || version > 34 compressed = version >= 31 ? true : false + 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) - sig64 = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1]) - pubkey = FFI::MemoryPointer.new(:uchar, pub_key_len = compressed ? 33 : 65) - pubkeylen = FFI::MemoryPointer.new(:int).write_int(pub_key_len) + recoverable_signature = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1]) - result = secp256k1_ecdsa_recover_compact(context, msg32, sig64, pubkey, pubkeylen, (compressed ? 1 : 0), recid) + internal_recoverable_signature = FFI::MemoryPointer.new(:uchar, 65) + result = secp256k1_ecdsa_recoverable_signature_parse_compact(context, internal_recoverable_signature, recoverable_signature, recid) + return nil unless result + internal_pubkey = FFI::MemoryPointer.new(:uchar, 64) + result = secp256k1_ecdsa_recover(context, internal_pubkey, internal_recoverable_signature, msg32) return nil unless result - pubkey.read_bytes(pubkeylen.read_int) + pubkey, pubkey_len = FFI::MemoryPointer.new(:uchar, 65), 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 || pubkey_len.read_uint64 > 0 + + pubkey.read_string(pubkey_len.read_uint64) end end end end