Module: Secp256k1

Includes:
C, EllSwift, MuSig, Recover, SchnorrSig
Included in:
MuSig::KeyAggContext, MuSig::Session
Defined in:
lib/secp256k1.rb,
lib/secp256k1/c.rb,
lib/secp256k1/musig.rb,
lib/secp256k1/version.rb,
lib/secp256k1/ellswift.rb,
lib/secp256k1/recovery.rb,
lib/secp256k1/schnorrsig.rb,
lib/secp256k1/musig/key_agg.rb,
lib/secp256k1/musig/session.rb

Overview

Binding for secp256k1 (github.com/bitcoin-core/secp256k1/)

Examples:

include Secp256k1

# Generate key pair
sk, pk = generate_key_pair

# Generate public key
pk = generate_pubkey(sk)

# sign and verify (ECDSA)
msg = Digest::SHA256.digest('message')
signature = sign_ecdsa(msg, sk)
verify_ecdsa(msg, signature, pk)

Defined Under Namespace

Modules: C, EllSwift, MuSig, Recover, SchnorrSig Classes: Error

Constant Summary collapse

FLAGS_TYPE_MASK =
((1 << 8) - 1)
FLAGS_TYPE_CONTEXT =
(1 << 0)
FLAGS_TYPE_COMPRESSION =
(1 << 1)
FLAGS_BIT_CONTEXT_VERIFY =
(1 << 8)
FLAGS_BIT_CONTEXT_SIGN =
(1 << 9)
FLAGS_BIT_COMPRESSION =
(1 << 8)
CONTEXT_VERIFY =

Flags to pass to context_create.

(FLAGS_TYPE_CONTEXT | FLAGS_BIT_CONTEXT_VERIFY)
CONTEXT_SIGN =
(FLAGS_TYPE_CONTEXT | FLAGS_BIT_CONTEXT_SIGN)
EC_COMPRESSED =

Flag to pass to ec_pubkey_serialize and ec_privkey_export.

(FLAGS_TYPE_COMPRESSION | FLAGS_BIT_COMPRESSION)
EC_UNCOMPRESSED =
(FLAGS_TYPE_COMPRESSION)
X_ONLY_PUBKEY_SIZE =
32
ELL_SWIFT_KEY_SIZE =
64
VERSION =
"0.2.0"

Instance Method Summary collapse

Methods included from MuSig

#aggregate_musig_nonce, #aggregate_pubkey, #generate_musig_nonce, #generate_musig_session_id

Methods included from EllSwift

#ellswift_create, #ellswift_decode, #ellswift_ecdh_xonly

Methods included from SchnorrSig

#sign_schnorr, #verify_schnorr

Methods included from Recover

#recover, #sign_recoverable

Methods included from C

ellswift_xdh_hash_function_bip324

Instance Method Details

#create_keypair(private_key) ⇒ String

Create key pair data from private key.

Parameters:

  • private_key (String)

    with hex format

Returns:

  • (String)

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

Raises:

  • (Secp256k1::Error)

    If private_key is invalid.

  • (ArgumentError)

    If invalid arguments specified.



128
129
130
131
132
133
134
135
136
137
138
# File 'lib/secp256k1.rb', line 128

def create_keypair(private_key)
  validate_string!("private_key", private_key, 32)
  private_key = hex2bin(private_key)
  with_context do |context|
    secret = FFI::MemoryPointer.new(:uchar, private_key.bytesize).put_bytes(0, private_key)
    raise Error, 'private_key is invalid.' unless secp256k1_ec_seckey_verify(context, secret)
    keypair = FFI::MemoryPointer.new(:uchar, 96)
    raise Error 'private_key is invalid.' unless secp256k1_keypair_create(context, keypair, secret) == 1
    keypair.read_string(96).unpack1('H*')
  end
end

#generate_key_pair(compressed: true) ⇒ Array

Randomly generate ec private key and public key.

Parameters:

  • compressed (Boolean) (defaults to: true)

    Whether to generate a compressed public key.

Returns:

  • (Array)

    Array of public key and public key (Both are hex values).

Raises:

  • (Secp256k1::Error)

    If secp256k1_ec_seckey_verify in generate_key_pair failed.



78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/secp256k1.rb', line 78

def generate_key_pair(compressed: true)
  with_context do |context|
    ret, tries, max = 0, 0, 20
    while ret != 1
      raise Error, 'secp256k1_ec_seckey_verify in generate_key_pair failed.' if tries >= max
      tries += 1
      private_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
      ret = secp256k1_ec_seckey_verify(context, private_key)
    end
    private_key =  private_key.read_string(32).unpack1('H*')
    [private_key , generate_pubkey_in_context(context,  private_key, compressed: compressed) ]
  end
end

#generate_pubkey(private_key, compressed: true) ⇒ String

Generate public key from private_key.

Parameters:

  • private_key (String)

    Private key with hex format.

  • compressed (Boolean) (defaults to: true)

    Whether to generate a compressed public key.

Returns:

  • (String)

    Public key with hex format.

Raises:

  • (ArgumentError)

    If invalid arguments specified.



97
98
99
100
101
102
103
# File 'lib/secp256k1.rb', line 97

def generate_pubkey(private_key, compressed: true)
  validate_string!("private_key", private_key, 32)
  private_key = hex2bin(private_key)
  with_context do |context|
    generate_pubkey_in_context(context, private_key, compressed: compressed)
  end
end

#parse_ec_pubkey?(pubkey, allow_hybrid = false) ⇒ Boolean

Validate whether this is a valid public key.

Parameters:

  • pubkey (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.

Raises:

  • (ArgumentError)

    If invalid arguments specified.



110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/secp256k1.rb', line 110

def parse_ec_pubkey?(pubkey, allow_hybrid = false)
  raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
  pubkey = hex2bin(pubkey)
  return false if !allow_hybrid && ![0x02, 0x03, 0x04].include?(pubkey[0].ord)
  with_context do |context|
    pubkey_size = pubkey.bytesize
    pubkey = FFI::MemoryPointer.new(:uchar, pubkey_size).put_bytes(0, pubkey)
    internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
    result = secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey, pubkey_size)
    result == 1
  end
end

#sign_ecdsa(data, private_key, extra_entropy = nil) ⇒ String

Sign to data using ecdsa.

Parameters:

  • data (String)

    The 32-byte message hash being signed with binary format.

  • private_key (String)

    a private key with hex format using sign.

  • extra_entropy (String) (defaults to: nil)

    (Optional)An extra entropy with binary format for rfc6979.

Returns:

  • (String)

    signature data with binary format. If unsupported algorithm specified, return nil.

Raises:

  • (ArgumentError)

    If invalid arguments specified.



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/secp256k1.rb', line 159

def sign_ecdsa(data, private_key, extra_entropy = nil)
  validate_string!("private_key", private_key, 32)
  validate_string!("data", data, 32)
  validate_string!("extra_entropy", extra_entropy, 32) if extra_entropy
  private_key = hex2bin(private_key)
  data = hex2bin(data)

  with_context do |context|
    secret = FFI::MemoryPointer.new(:uchar, private_key.bytesize).put_bytes(0, private_key)
    raise Error, 'private_key is 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 Error, '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 Error, 'secp256k1_ecdsa_signature_serialize_der failed' unless result

    signature.read_string(signature_len.read_uint64)
  end
end

#valid_xonly_pubkey?(pubkey) ⇒ Boolean

Check whether valid x-only public key or not.

Parameters:

  • pubkey (String)

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

Returns:

  • (Boolean)

    result.



143
144
145
146
147
148
149
150
151
# File 'lib/secp256k1.rb', line 143

def valid_xonly_pubkey?(pubkey)
  return false unless pubkey.is_a?(String)
  begin
    full_pubkey_from_xonly_pubkey(hex2bin(pubkey))
  rescue Exception
    return false
  end
  true
end

#verify_ecdsa(data, signature, pubkey) ⇒ Boolean

Verify ecdsa signature.

Parameters:

  • data (String)

    The 32-byte message hash assumed to be signed.

  • signature (String)

    signature data with binary format

  • pubkey (String)

    a public key with hex format using verify.

Returns:

  • (Boolean)

    verification result.

Raises:

  • (ArgumentError)

    If invalid arguments specified.



197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/secp256k1.rb', line 197

def verify_ecdsa(data, signature, pubkey)
  raise ArgumentError, "sig must be String." unless signature.is_a?(String)
  raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
  validate_string!("data", data, 32)
  data = hex2bin(data)
  pubkey = hex2bin(pubkey)
  signature = hex2bin(signature)
  with_context do |context|
    return false if data.bytesize == 0
    pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
    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, signature.bytesize).put_bytes(0, signature)
    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: (CONTEXT_VERIFY | CONTEXT_SIGN)) ⇒ Object

Creates a secp256k1 context object, performs the operations passed in the block, and then ensures that the secp256k1 context object is destroyed at the end.

Parameters:

  • flags (Integer) (defaults to: (CONTEXT_VERIFY | CONTEXT_SIGN))

    The flag to use when performing the operation.

Raises:



59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/secp256k1.rb', line 59

def with_context(flags: (CONTEXT_VERIFY | CONTEXT_SIGN))
  begin
    context = secp256k1_context_create(flags)
    ret, tries, max = 0, 0, 20
    while ret != 1
      raise Error, '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