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-core/secp256k1/) tag: v0.4.0 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

.create_keypair(priv_key) ⇒ String

Create key pair data from private key.

Parameters:

  • priv_key (String)

    with hex format

Returns:

  • (String)

    key pair data with hex format. data = private key(32 bytes) | public key(64 bytes).



210
211
212
213
214
215
216
217
218
219
# File 'lib/bitcoin/secp256k1/native.rb', line 210

def create_keypair(priv_key)
  with_context do |context|
    priv_key = priv_key.htb
    secret = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
    raise 'priv_key is invalid.' unless secp256k1_ec_seckey_verify(context, secret)
    keypair = FFI::MemoryPointer.new(:uchar, 96)
    raise 'priv_key is invalid.' unless secp256k1_keypair_create(context, keypair, secret) == 1
    keypair.read_string(96).bth
  end
end

.ellswift_create(priv_key) ⇒ String

Compute an ElligatorSwift public key for a secret key.

Parameters:

  • priv_key (String)

    private key with hex format

Returns:

  • (String)

    ElligatorSwift public key with hex format.



249
250
251
252
253
254
255
256
257
# File 'lib/bitcoin/secp256k1/native.rb', line 249

def ellswift_create(priv_key)
  with_context(flags: SECP256K1_CONTEXT_SIGN) do |context|
    ell64 = FFI::MemoryPointer.new(:uchar, 64)
    seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key.htb)
    result = secp256k1_ellswift_create(context, ell64, seckey32, nil)
    raise ArgumentError, 'Failed to create ElligatorSwift public key.' unless result == 1
    ell64.read_string(64).bth
  end
end

.ellswift_decode(ell_key) ⇒ String

Decode ellswift public key.

Parameters:

  • ell_key (String)

    ElligatorSwift key with binary format.

Returns:

  • (String)

    Decoded public key with hex format



236
237
238
239
240
241
242
243
244
# File 'lib/bitcoin/secp256k1/native.rb', line 236

def ellswift_decode(ell_key)
  with_context do |context|
    ell64 = FFI::MemoryPointer.new(:uchar, ell_key.bytesize).put_bytes(0, ell_key)
    internal = FFI::MemoryPointer.new(:uchar, 64)
    result = secp256k1_ellswift_decode(context, internal, ell64)
    raise ArgumentError, 'Decode failed.' unless result == 1
    serialize_pubkey_internal(context, internal, true)
  end
end

.ellswift_ecdh_xonly(their_ell_pubkey, our_ell_pubkey, priv_key, initiating) ⇒ String

Compute X coordinate of shared ECDH point between elswift pubkey and privkey.

Parameters:

Returns:

  • (String)

    x coordinate with hex format.



265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
# File 'lib/bitcoin/secp256k1/native.rb', line 265

def ellswift_ecdh_xonly(their_ell_pubkey, our_ell_pubkey, priv_key, initiating)
  with_context(flags: SECP256K1_CONTEXT_SIGN) do |context|
    output = FFI::MemoryPointer.new(:uchar, 32)
    our_ell_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, our_ell_pubkey.key)
    their_ell_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, their_ell_pubkey.key)
    seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, priv_key.htb)
    hashfp = secp256k1_ellswift_xdh_hash_function_bip324
    result = secp256k1_ellswift_xdh(context, output,
                                    initiating ? our_ell_ptr : their_ell_ptr,
                                    initiating ? their_ell_ptr : our_ell_ptr,
                                    seckey32,
                                    initiating ? 0 : 1,
                                    hashfp, nil)
    raise ArgumentError, "secret was invalid or hashfp returned 0" unless result == 1
    output.read_string(32).bth
  end
end

.generate_key(compressed: true) ⇒ Object

generate bitcoin key object



101
102
103
104
# File 'lib/bitcoin/secp256k1/native.rb', line 101

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



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/bitcoin/secp256k1/native.rb', line 86

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



106
107
108
109
110
# File 'lib/bitcoin/secp256k1/native.rb', line 106

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
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# 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)
  attach_function(:secp256k1_schnorrsig_sign32, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
  attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ecdsa_sign_recoverable, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
  attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
  attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
  # Define function pointer
  callback(:secp256k1_ellswift_xdh_hash_function, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
  attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :secp256k1_ellswift_xdh_hash_function)
  attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
end

.parse_ec_pubkey?(pub_key, allow_hybrid = false) ⇒ Boolean

# validate whether this is a valid public key (more expensive than IsValid())

Parameters:

  • pub_key (String)

    public key with hex format.

  • allow_hybrid (Boolean) (defaults to: false)

    whether support hybrid public key.

Returns:

  • (Boolean)

    If valid public key return true, otherwise false.



196
197
198
199
200
201
202
203
204
205
# File 'lib/bitcoin/secp256k1/native.rb', line 196

def parse_ec_pubkey?(pub_key, allow_hybrid = false)
  pub_key = pub_key.htb
  return false if !allow_hybrid && ![0x02, 0x03, 0x04].include?(pub_key[0].ord)
  with_context do |context|
    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, pub_key.bytesize)
    result == 1
  end
end

.recover_compact(data, signature, rec, compressed) ⇒ Bitcoin::Key

Recover public key from compact signature.

Parameters:

  • data (String)

    message digest using signature.

  • signature (String)

    signature with binary format.

  • rec (Integer)

    recovery id.

  • compressed (Boolean)

    whether compressed public key or not.

Returns:



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/bitcoin/secp256k1/native.rb', line 158

def recover_compact(data, signature, rec, compressed)
  with_context do |context|
    sig = FFI::MemoryPointer.new(:uchar, 65)
    input = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
    result = secp256k1_ecdsa_recoverable_signature_parse_compact(context, sig, input, rec)
    raise 'secp256k1_ecdsa_recoverable_signature_parse_compact failed.' unless result == 1

    pubkey = FFI::MemoryPointer.new(:uchar, 64)
    msg = FFI::MemoryPointer.new(:uchar, data.bytesize).put_bytes(0, data)
    result = secp256k1_ecdsa_recover(context, pubkey, sig, msg)
    raise 'secp256k1_ecdsa_recover failed.' unless result == 1

    pubkey = serialize_pubkey_internal(context, pubkey.read_string(64), compressed)
    Bitcoin::Key.new(pubkey: pubkey, compressed: compressed)
  end
end

.sign_compact(data, privkey) ⇒ Array[signature, recovery id]

Sign data with compact format.

Parameters:

  • data (String)

    a data to be signed with binary format

  • privkey (String)

    a private key using sign with hex format

Returns:

  • (Array[signature, recovery id])


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

def sign_compact(data, privkey)
  with_context do |context|
    sig = FFI::MemoryPointer.new(:uchar, 65)
    hash =FFI::MemoryPointer.new(:uchar, data.bytesize).put_bytes(0, data)
    priv_key = privkey.htb
    sec_key = FFI::MemoryPointer.new(:uchar, priv_key.bytesize).put_bytes(0, priv_key)
    result = secp256k1_ecdsa_sign_recoverable(context, sig, hash, sec_key, nil, nil)
    raise 'secp256k1_ecdsa_sign_recoverable failed.' if result == 0

    output = FFI::MemoryPointer.new(:uchar, 64)
    rec = FFI::MemoryPointer.new(:uint64)
    result = secp256k1_ecdsa_recoverable_signature_serialize_compact(context, output, rec, sig)
    raise 'secp256k1_ecdsa_recoverable_signature_serialize_compact failed.' unless result == 1

    raw_sig = output.read_string(64)
    [ECDSA::Signature.new(raw_sig[0...32].bti, raw_sig[32..-1].bti), rec.read(:int)]
  end
end

.sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa) ⇒ String

sign data.

Parameters:

  • data (String)

    a data to be signed with binary format

  • privkey (String)

    a private key with hex format using sign

  • extra_entropy (String) (defaults to: nil)

    a extra entropy with binary format for rfc6979

  • algo (Symbol) (defaults to: :ecdsa)

    signature algorithm. ecdsa(default) or schnorr.

Returns:

  • (String)

    signature data with binary format



118
119
120
121
122
123
124
125
126
127
# File 'lib/bitcoin/secp256k1/native.rb', line 118

def sign_data(data, privkey, extra_entropy = nil, algo: :ecdsa)
  case algo
  when :ecdsa
    sign_ecdsa(data, privkey, extra_entropy)
  when :schnorr
    sign_schnorr(data, privkey, extra_entropy)
  else
    nil
  end
end

.valid_xonly_pubkey?(pub_key) ⇒ Boolean

Check whether valid x-only public key or not.

Parameters:

  • pub_key (String)

    x-only public key with hex format(32 bytes).

Returns:

  • (Boolean)

    result.



224
225
226
227
228
229
230
231
# File 'lib/bitcoin/secp256k1/native.rb', line 224

def valid_xonly_pubkey?(pub_key)
  begin
    full_pubkey_from_xonly_pubkey(pub_key)
  rescue Exception
    return false
  end
  true
end

.verify_sig(data, sig, pubkey, algo: :ecdsa) ⇒ Boolean

verify signature # @param [Symbol] algo signature algorithm. ecdsa(default) or schnorr.

Parameters:

  • data (String)

    a data with binary format.

  • sig (String)

    signature data with binary format

  • pubkey (String)

    a public key with hex format using verify.

Returns:

  • (Boolean)

    verification result.



181
182
183
184
185
186
187
188
189
190
# File 'lib/bitcoin/secp256k1/native.rb', line 181

def verify_sig(data, sig, pubkey, algo: :ecdsa)
  case algo
  when :ecdsa
    verify_ecdsa(data, sig, pubkey)
  when :schnorr
    verify_schnorr(data, sig, pubkey)
  else
    false
  end
end

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



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

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