Module: Secp256k1::Key

Included in:
Secp256k1
Defined in:
lib/secp256k1/key.rb

Overview

Key module provides EC key arithmetic (tweak/negate/combine), x-only public key operations used by Taproot, and key pair accessors.

Examples:

include Secp256k1

sk, pk = generate_key_pair
tweak = SecureRandom.bytes(32)

# Tweak a private/public key.
tweaked_sk = tweak_add_seckey(sk, tweak)
tweaked_pk = tweak_add_pubkey(pk, tweak)

Instance Method Summary collapse

Instance Method Details

#combine_pubkeys(pubkeys, compressed: true) ⇒ String

Add a number of public keys together.

Parameters:

  • pubkeys (Array<String>)

    an array of public keys with hex format.

  • compressed (Boolean) (defaults to: true)

    Whether to return a compressed public key.

Returns:

  • (String)

    the sum of the public keys with hex format.

Raises:

  • (Secp256k1::Error)

    If the sum is the point at infinity.

  • (ArgumentError)

    If invalid arguments specified.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/secp256k1/key.rb', line 95

def combine_pubkeys(pubkeys, compressed: true)
  raise ArgumentError, "pubkeys must be Array." unless pubkeys.is_a?(Array)
  raise ArgumentError, "pubkeys must not be empty." if pubkeys.empty?
  with_context do |context|
    internals = pubkeys.map do |pubkey|
      raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
      parse_pubkey_internal(context, hex2bin(pubkey))
    end
    ins = FFI::MemoryPointer.new(:pointer, internals.size)
    ins.write_array_of_pointer(internals)
    combined = FFI::MemoryPointer.new(:uchar, 64)
    raise Error, 'secp256k1_ec_pubkey_combine failed.' unless secp256k1_ec_pubkey_combine(context, combined, ins, internals.size) == 1
    serialize_pubkey_internal(context, combined, compressed)
  end
end

#compare_pubkey(pubkey1, pubkey2) ⇒ Integer

Compare two public keys using lexicographic(of compressed serialization) order.

Parameters:

  • pubkey1 (String)

    a public key with hex format.

  • pubkey2 (String)

    a public key with hex format.

Returns:

  • (Integer)

    negative if pubkey1 < pubkey2, 0 if equal, positive if pubkey1 > pubkey2.

Raises:

  • (Secp256k1::Error)

    If a public key is invalid.

  • (ArgumentError)

    If invalid arguments specified.



233
234
235
236
237
238
239
240
241
# File 'lib/secp256k1/key.rb', line 233

def compare_pubkey(pubkey1, pubkey2)
  raise ArgumentError, "pubkey1 must be String." unless pubkey1.is_a?(String)
  raise ArgumentError, "pubkey2 must be String." unless pubkey2.is_a?(String)
  with_context do |context|
    a = parse_pubkey_internal(context, hex2bin(pubkey1))
    b = parse_pubkey_internal(context, hex2bin(pubkey2))
    secp256k1_ec_pubkey_cmp(context, a, b)
  end
end

#compare_xonly_pubkey(pubkey1, pubkey2) ⇒ Integer

Compare two x-only public keys using lexicographic order.

Parameters:

  • pubkey1 (String)

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

  • pubkey2 (String)

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

Returns:

  • (Integer)

    negative if pubkey1 < pubkey2, 0 if equal, positive if pubkey1 > pubkey2.

Raises:

  • (Secp256k1::Error)

    If a public key is invalid.

  • (ArgumentError)

    If invalid arguments specified.



270
271
272
273
274
275
276
277
278
# File 'lib/secp256k1/key.rb', line 270

def compare_xonly_pubkey(pubkey1, pubkey2)
  validate_string!("pubkey1", pubkey1, X_ONLY_PUBKEY_SIZE)
  validate_string!("pubkey2", pubkey2, X_ONLY_PUBKEY_SIZE)
  with_context do |context|
    a = parse_xonly_pubkey_internal(context, hex2bin(pubkey1))
    b = parse_xonly_pubkey_internal(context, hex2bin(pubkey2))
    secp256k1_xonly_pubkey_cmp(context, a, b)
  end
end

#keypair_to_pubkey(keypair, compressed: true) ⇒ String

Get the public key from a key pair.

Parameters:

  • keypair (String)

    a key pair with hex format(96 bytes), created by Secp256k1#create_keypair.

  • compressed (Boolean) (defaults to: true)

    Whether to return a compressed public key.

Returns:

  • (String)

    public key with hex format.

Raises:

  • (Secp256k1::Error)

    If the key pair is invalid.

  • (ArgumentError)

    If invalid arguments specified.



183
184
185
186
187
188
189
190
191
192
# File 'lib/secp256k1/key.rb', line 183

def keypair_to_pubkey(keypair, compressed: true)
  validate_string!("keypair", keypair, 96)
  keypair = hex2bin(keypair)
  with_context do |context|
    keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
    internal = FFI::MemoryPointer.new(:uchar, 64)
    raise Error, 'secp256k1_keypair_pub failed.' unless secp256k1_keypair_pub(context, internal, keypair_ptr) == 1
    serialize_pubkey_internal(context, internal, compressed)
  end
end

#keypair_to_seckey(keypair) ⇒ String

Get the private key from a key pair.

Parameters:

Returns:

  • (String)

    private key with hex format.

Raises:

  • (Secp256k1::Error)

    If the key pair is invalid.

  • (ArgumentError)

    If invalid arguments specified.



199
200
201
202
203
204
205
206
207
208
# File 'lib/secp256k1/key.rb', line 199

def keypair_to_seckey(keypair)
  validate_string!("keypair", keypair, 96)
  keypair = hex2bin(keypair)
  with_context do |context|
    keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
    seckey = FFI::MemoryPointer.new(:uchar, 32)
    raise Error, 'secp256k1_keypair_sec failed.' unless secp256k1_keypair_sec(context, seckey, keypair_ptr) == 1
    seckey.read_string(32).unpack1('H*')
  end
end

#keypair_to_xonly_pubkey(keypair) ⇒ Array(String, Integer)

Get the x-only public key from a key pair.

Parameters:

Returns:

  • (Array(String, Integer))

    the x-only public key with hex format(32 bytes) and its parity(0 or 1).

Raises:

  • (Secp256k1::Error)

    If the key pair is invalid.

  • (ArgumentError)

    If invalid arguments specified.



215
216
217
218
219
220
221
222
223
224
225
# File 'lib/secp256k1/key.rb', line 215

def keypair_to_xonly_pubkey(keypair)
  validate_string!("keypair", keypair, 96)
  keypair = hex2bin(keypair)
  with_context do |context|
    keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
    xonly = FFI::MemoryPointer.new(:uchar, 64)
    parity = FFI::MemoryPointer.new(:int)
    raise Error, 'secp256k1_keypair_xonly_pub failed.' unless secp256k1_keypair_xonly_pub(context, xonly, parity, keypair_ptr) == 1
    [serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
  end
end

#keypair_xonly_tweak_add(keypair, tweak) ⇒ String

Tweak a key pair by adding tweak to the key pair’s x-only context.

Parameters:

  • keypair (String)

    a key pair with hex format(96 bytes), created by Secp256k1#create_keypair.

  • tweak (String)

    a 32-byte tweak with hex format.

Returns:

  • (String)

    the tweaked key pair with hex format(96 bytes).

Raises:

  • (Secp256k1::Error)

    If the arguments are invalid.

  • (ArgumentError)

    If invalid arguments specified.



286
287
288
289
290
291
292
293
294
295
296
297
# File 'lib/secp256k1/key.rb', line 286

def keypair_xonly_tweak_add(keypair, tweak)
  validate_string!("keypair", keypair, 96)
  validate_string!("tweak", tweak, 32)
  keypair = hex2bin(keypair)
  tweak = hex2bin(tweak)
  with_context do |context|
    keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
    tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
    raise Error, 'secp256k1_keypair_xonly_tweak_add failed.' unless secp256k1_keypair_xonly_tweak_add(context, keypair_ptr, tweak_ptr) == 1
    keypair_ptr.read_string(96).unpack1('H*')
  end
end

#negate_pubkey(pubkey, compressed: true) ⇒ String

Negate a public key.

Parameters:

  • pubkey (String)

    a public key with hex format.

  • compressed (Boolean) (defaults to: true)

    Whether to return a compressed public key.

Returns:

  • (String)

    negated public key with hex format.

Raises:

  • (Secp256k1::Error)

    If the public key is invalid.

  • (ArgumentError)

    If invalid arguments specified.



57
58
59
60
61
62
63
64
65
# File 'lib/secp256k1/key.rb', line 57

def negate_pubkey(pubkey, compressed: true)
  raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
  pubkey = hex2bin(pubkey)
  with_context do |context|
    internal = parse_pubkey_internal(context, pubkey)
    raise Error, 'secp256k1_ec_pubkey_negate failed.' unless secp256k1_ec_pubkey_negate(context, internal) == 1
    serialize_pubkey_internal(context, internal, compressed)
  end
end

#negate_seckey(private_key) ⇒ String

Negate a private key in place and return the result.

Parameters:

  • private_key (String)

    a private key with hex format.

Returns:

  • (String)

    negated private key with hex format.

Raises:

  • (Secp256k1::Error)

    If the private key is invalid.

  • (ArgumentError)

    If invalid arguments specified.



21
22
23
24
25
26
27
28
29
# File 'lib/secp256k1/key.rb', line 21

def negate_seckey(private_key)
  validate_string!("private_key", private_key, 32)
  private_key = hex2bin(private_key)
  with_context do |context|
    seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
    raise Error, 'secp256k1_ec_seckey_negate failed.' unless secp256k1_ec_seckey_negate(context, seckey) == 1
    seckey.read_string(32).unpack1('H*')
  end
end

#sort_pubkeys(pubkeys, compressed: true) ⇒ Array<String>

Sort public keys using lexicographic(of compressed serialization) order.

Parameters:

  • pubkeys (Array<String>)

    an array of public keys with hex format.

  • compressed (Boolean) (defaults to: true)

    Whether to return compressed public keys.

Returns:

  • (Array<String>)

    the sorted public keys with hex format.

Raises:

  • (Secp256k1::Error)

    If a public key is invalid.

  • (ArgumentError)

    If invalid arguments specified.



249
250
251
252
253
254
255
256
257
258
259
260
261
262
# File 'lib/secp256k1/key.rb', line 249

def sort_pubkeys(pubkeys, compressed: true)
  raise ArgumentError, "pubkeys must be Array." unless pubkeys.is_a?(Array)
  raise ArgumentError, "pubkeys must not be empty." if pubkeys.empty?
  with_context do |context|
    internals = pubkeys.map do |pubkey|
      raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
      parse_pubkey_internal(context, hex2bin(pubkey))
    end
    arr = FFI::MemoryPointer.new(:pointer, internals.size)
    arr.write_array_of_pointer(internals)
    raise Error, 'secp256k1_ec_pubkey_sort failed.' unless secp256k1_ec_pubkey_sort(context, arr, internals.size) == 1
    arr.read_array_of_pointer(internals.size).map { |ptr| serialize_pubkey_internal(context, ptr, compressed) }
  end
end

#tweak_add_pubkey(pubkey, tweak, compressed: true) ⇒ String

Tweak a public key by adding tweak * G to it.

Parameters:

  • pubkey (String)

    a public key with hex format.

  • tweak (String)

    a 32-byte tweak with hex format.

  • compressed (Boolean) (defaults to: true)

    Whether to return a compressed public key.

Returns:

  • (String)

    tweaked public key with hex format.

Raises:

  • (Secp256k1::Error)

    If the arguments are invalid or the result is the point at infinity.

  • (ArgumentError)

    If invalid arguments specified.



74
75
76
# File 'lib/secp256k1/key.rb', line 74

def tweak_add_pubkey(pubkey, tweak, compressed: true)
  tweak_pubkey(pubkey, tweak, :secp256k1_ec_pubkey_tweak_add, compressed: compressed)
end

#tweak_add_seckey(private_key, tweak) ⇒ String

Tweak a private key by adding tweak to it.

Parameters:

  • private_key (String)

    a private key with hex format.

  • tweak (String)

    a 32-byte tweak with hex format.

Returns:

  • (String)

    tweaked private key with hex format.

Raises:

  • (Secp256k1::Error)

    If the arguments are invalid or the result is the zero key.

  • (ArgumentError)

    If invalid arguments specified.



37
38
39
# File 'lib/secp256k1/key.rb', line 37

def tweak_add_seckey(private_key, tweak)
  tweak_seckey(private_key, tweak, :secp256k1_ec_seckey_tweak_add)
end

#tweak_mul_pubkey(pubkey, tweak, compressed: true) ⇒ String

Tweak a public key by multiplying it by tweak.

Parameters:

  • pubkey (String)

    a public key with hex format.

  • tweak (String)

    a 32-byte tweak with hex format.

  • compressed (Boolean) (defaults to: true)

    Whether to return a compressed public key.

Returns:

  • (String)

    tweaked public key with hex format.

Raises:

  • (Secp256k1::Error)

    If the arguments are invalid.

  • (ArgumentError)

    If invalid arguments specified.



85
86
87
# File 'lib/secp256k1/key.rb', line 85

def tweak_mul_pubkey(pubkey, tweak, compressed: true)
  tweak_pubkey(pubkey, tweak, :secp256k1_ec_pubkey_tweak_mul, compressed: compressed)
end

#tweak_mul_seckey(private_key, tweak) ⇒ String

Tweak a private key by multiplying it by tweak.

Parameters:

  • private_key (String)

    a private key with hex format.

  • tweak (String)

    a 32-byte tweak with hex format.

Returns:

  • (String)

    tweaked private key with hex format.

Raises:

  • (Secp256k1::Error)

    If the arguments are invalid.

  • (ArgumentError)

    If invalid arguments specified.



47
48
49
# File 'lib/secp256k1/key.rb', line 47

def tweak_mul_seckey(private_key, tweak)
  tweak_seckey(private_key, tweak, :secp256k1_ec_seckey_tweak_mul)
end

#xonly_pubkey_from_pubkey(pubkey) ⇒ Array(String, Integer)

Convert a public key into an x-only public key.

Parameters:

  • pubkey (String)

    a public key with hex format.

Returns:

  • (Array(String, Integer))

    the x-only public key with hex format(32 bytes) and its parity(0 or 1).

Raises:

  • (Secp256k1::Error)

    If the public key is invalid.

  • (ArgumentError)

    If invalid arguments specified.



116
117
118
119
120
121
122
123
124
125
126
# File 'lib/secp256k1/key.rb', line 116

def xonly_pubkey_from_pubkey(pubkey)
  raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
  pubkey = hex2bin(pubkey)
  with_context do |context|
    internal = parse_pubkey_internal(context, pubkey)
    xonly = FFI::MemoryPointer.new(:uchar, 64)
    parity = FFI::MemoryPointer.new(:int)
    raise Error, 'secp256k1_xonly_pubkey_from_pubkey failed.' unless secp256k1_xonly_pubkey_from_pubkey(context, xonly, parity, internal) == 1
    [serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
  end
end

#xonly_tweak_add_check?(tweaked_pubkey32, tweaked_pk_parity, internal_pubkey, tweak) ⇒ Boolean

Check that a tweaked x-only public key was computed by tweaking internal_pubkey with tweak.

Parameters:

  • tweaked_pubkey32 (String)

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

  • tweaked_pk_parity (Integer)

    the parity(0 or 1) of the tweaked public key.

  • internal_pubkey (String)

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

  • tweak (String)

    a 32-byte tweak with hex format.

Returns:

  • (Boolean)

    verification result.

Raises:

  • (ArgumentError)

    If invalid arguments specified.



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

def xonly_tweak_add_check?(tweaked_pubkey32, tweaked_pk_parity, internal_pubkey, tweak)
  validate_string!("tweaked_pubkey32", tweaked_pubkey32, X_ONLY_PUBKEY_SIZE)
  validate_string!("internal_pubkey", internal_pubkey, X_ONLY_PUBKEY_SIZE)
  validate_string!("tweak", tweak, 32)
  raise ArgumentError, "tweaked_pk_parity must be 0 or 1." unless [0, 1].include?(tweaked_pk_parity)
  tweaked_pubkey32 = hex2bin(tweaked_pubkey32)
  internal_pubkey = hex2bin(internal_pubkey)
  tweak = hex2bin(tweak)
  with_context do |context|
    internal = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, internal_pubkey)
    internal_xonly = FFI::MemoryPointer.new(:uchar, 64)
    return false unless secp256k1_xonly_pubkey_parse(context, internal_xonly, internal) == 1
    tweaked_ptr = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, tweaked_pubkey32)
    tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
    secp256k1_xonly_pubkey_tweak_add_check(context, tweaked_ptr, tweaked_pk_parity, internal_xonly, tweak_ptr) == 1
  end
end

#xonly_tweak_add_pubkey(xonly_pubkey, tweak, compressed: true) ⇒ Array(String, Integer)

Tweak an x-only public key by adding tweak * G to it (used for Taproot output key derivation).

Parameters:

  • xonly_pubkey (String)

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

  • tweak (String)

    a 32-byte tweak with hex format.

  • compressed (Boolean) (defaults to: true)

    Whether to return a compressed public key.

Returns:

  • (Array(String, Integer))

    the tweaked(full) public key with hex format and the parity(0 or 1) of the tweaked key.

Raises:

  • (Secp256k1::Error)

    If the arguments are invalid.

  • (ArgumentError)

    If invalid arguments specified.



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

def xonly_tweak_add_pubkey(xonly_pubkey, tweak, compressed: true)
  validate_string!("xonly_pubkey", xonly_pubkey, X_ONLY_PUBKEY_SIZE)
  validate_string!("tweak", tweak, 32)
  xonly_pubkey = hex2bin(xonly_pubkey)
  tweak = hex2bin(tweak)
  with_context do |context|
    xonly = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, xonly_pubkey)
    internal = FFI::MemoryPointer.new(:uchar, 64)
    raise Error, 'An invalid x-only public key was specified.' unless secp256k1_xonly_pubkey_parse(context, internal, xonly) == 1
    tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
    output = FFI::MemoryPointer.new(:uchar, 64)
    raise Error, 'secp256k1_xonly_pubkey_tweak_add failed.' unless secp256k1_xonly_pubkey_tweak_add(context, output, internal, tweak_ptr) == 1
    _, parity = xonly_pubkey_from_internal(context, output)
    [serialize_pubkey_internal(context, output, compressed), parity]
  end
end