Module: Bitcoin::Secp256k1::Native

Extended by:
Native
Includes:
FFI::Library
Included in:
Native
Defined in:
lib/bitcoin/secp256k1/native.rb

Overview

binding for secp256k1 (github.com/bitcoin/bitcoin/tree/v0.14.2/src/secp256k1) tag: v0.14.2 this is not included by default, to enable set shared object path to ENV for linux, ENV = ‘/usr/local/lib/libsecp256k1.so’ for mac,

Constant Summary collapse

SECP256K1_FLAGS_TYPE_MASK =
((1 << 8) - 1)
SECP256K1_FLAGS_TYPE_CONTEXT =
(1 << 0)
SECP256K1_FLAGS_TYPE_COMPRESSION =
(1 << 1)
SECP256K1_FLAGS_BIT_CONTEXT_VERIFY =
(1 << 8)
SECP256K1_FLAGS_BIT_CONTEXT_SIGN =
(1 << 9)
SECP256K1_FLAGS_BIT_COMPRESSION =
(1 << 8)
SECP256K1_CONTEXT_VERIFY =

Flags to pass to secp256k1_context_create.

(SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_VERIFY)
SECP256K1_CONTEXT_SIGN =
(SECP256K1_FLAGS_TYPE_CONTEXT | SECP256K1_FLAGS_BIT_CONTEXT_SIGN)
SECP256K1_EC_COMPRESSED =

Flag to pass to secp256k1_ec_pubkey_serialize and secp256k1_ec_privkey_export.

(SECP256K1_FLAGS_TYPE_COMPRESSION | SECP256K1_FLAGS_BIT_COMPRESSION)
SECP256K1_EC_UNCOMPRESSED =
(SECP256K1_FLAGS_TYPE_COMPRESSION)

Class Method Summary collapse

Class Method Details

.generate_key(compressed: true) ⇒ Object

generate bitcoin key object



87
88
89
90
# File 'lib/bitcoin/secp256k1/native.rb', line 87

def generate_key(compressed: true)
  privkey, pubkey = generate_key_pair(compressed: compressed)
  Bitcoin::Key.new(priv_key: privkey, pubkey: pubkey, compressed: compressed)
end

.generate_key_pair(compressed: true) ⇒ Object

generate ec private key and public key



72
73
74
75
76
77
78
79
80
81
82
83
84
# File 'lib/bitcoin/secp256k1/native.rb', line 72

def generate_key_pair(compressed: true)
  with_context do |context|
    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)
    end
    private_key =  priv_key.read_string(32).bth
    [private_key , generate_pubkey_in_context(context,  private_key, compressed: compressed) ]
  end
end

.generate_pubkey(priv_key, compressed: true) ⇒ Object



92
93
94
95
96
# File 'lib/bitcoin/secp256k1/native.rb', line 92

def generate_pubkey(priv_key, compressed: true)
  with_context do |context|
    generate_pubkey_in_context(context, priv_key, compressed: compressed)
  end
end

.initObject



34
35
36
37
38
# File 'lib/bitcoin/secp256k1/native.rb', line 34

def init
  raise 'secp256k1 library dose not found.' unless File.exist?(ENV['SECP256K1_LIB_PATH'])
  ffi_lib(ENV['SECP256K1_LIB_PATH'])
  load_functions
end

.load_functionsObject



40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/bitcoin/secp256k1/native.rb', line 40

def load_functions
  attach_function(:secp256k1_context_create, [:uint], :pointer)
  attach_function(:secp256k1_context_destroy, [:pointer], :void)
  attach_function(:secp256k1_context_randomize, [:pointer, :pointer], :int)
  attach_function(:secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ec_seckey_verify, [:pointer, :pointer], :int)
  attach_function(:secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int)
  attach_function(:secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int)
  attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
  attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
end

.sign_data(data, privkey, extra_entropy) ⇒ String

sign data.

Parameters:

  • data (String)

    a data to be signed with binary format

  • privkey (String)

    a private key using sign

  • extra_entropy (String)

    a extra entropy for rfc6979

Returns:

  • (String)

    signature data with binary format



103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# File 'lib/bitcoin/secp256k1/native.rb', line 103

def sign_data(data, privkey, extra_entropy)
  with_context do |context|
    secret = FFI::MemoryPointer.new(:uchar, privkey.htb.bytesize).put_bytes(0, privkey.htb)
    raise 'priv_key invalid' unless secp256k1_ec_seckey_verify(context, secret)

    internal_signature = FFI::MemoryPointer.new(:uchar, 64)
    msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
    entropy = extra_entropy ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, extra_entropy) : 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, secret, nil, entropy)
    end

    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

    signature.read_string(signature_len.read_uint64)
  end
end

.verify_sig(data, sig, pub_key) ⇒ Object



129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# File 'lib/bitcoin/secp256k1/native.rb', line 129

def verify_sig(data, sig, pub_key)
  with_context do |context|
    return false if data.bytesize == 0

    pubkey = FFI::MemoryPointer.new(:uchar, pub_key.htb.bytesize).put_bytes(0, pub_key.htb)
    internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
    result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey.size)
    return false unless result

    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)
    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)

    result == 1
  end
end

.with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN)) ⇒ Object



55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/bitcoin/secp256k1/native.rb', line 55

def with_context(flags: (SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN))
  init
  begin
    context = secp256k1_context_create(flags)
    ret, tries, max = 0, 0, 20
    while ret != 1
      raise 'secp256k1_context_randomize failed.' if tries >= max
      tries += 1
      ret = secp256k1_context_randomize(context, FFI::MemoryPointer.from_string(SecureRandom.random_bytes(32)))
    end
    yield(context) if block_given?
  ensure
    secp256k1_context_destroy(context)
  end
end