secp256k1rb 0.2.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11620dd56323837c51b4adaf78b80e683c039b6086af0d501df6890f31c32e07
4
- data.tar.gz: 27b5544236685afd32b11dd636d1f78ca626cfb63a120c6da88504238aa0c6b2
3
+ metadata.gz: ecd7e009765f35dce95f533c357900c4a7c4d6018cbd1f9dd3139af22b4e7c85
4
+ data.tar.gz: 0f6868a1f010cbca12e4ef7b4efe66713ae46fa6a28faf958dc3ee0ec529c8a9
5
5
  SHA512:
6
- metadata.gz: 14177d6aa88872ae15db021b45d492ec59b083ad751d224d852c9f5440fd2a9b51855608a0c0456175aadd09b06d68591122b799fc2a7d08724c4f726c84c980
7
- data.tar.gz: '0805a6d29bad9054f581115919365a3e8623adc8d93f64e92d18435f33012056a227ccbb4bd08c5421ec4a3bbc1e85f94d8b116cd47d4c205f51e0efe5306f00'
6
+ metadata.gz: b498f4d026083bd102410e7a8fff7cf2cb59b7f9b0f7033e0bcad9838d9e5fe718f87b9a2c9f8cb2f23439c66a4c612969d6f011fe00c24ce1a426c4a065c661
7
+ data.tar.gz: 5f1ae0a7a15d18af2c92d35ac93718c5f5d3733fd8236197277d7ba03bef3efb370560671f704f34b7b80d3ae61d80b3fc953a8b88246a4520152ab322b435b1
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ secp256k1
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-4.0.0
data/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.8.0]
4
+
5
+ - Change versioning policy: the gem's major and minor version now match the
6
+ supported secp256k1 version. Patch releases are used for gem-side changes.
7
+ - Support secp256k1 v0.8.0.
8
+ - Add `Secp256k1::SilentPayments` module (BIP-352 Silent Payments):
9
+ `silentpayments_sender_create_outputs`, `silentpayments_create_label`,
10
+ `silentpayments_create_labeled_spend_pubkey`, `silentpayments_create_prevouts_summary` and
11
+ `silentpayments_scan_outputs`. Tested against the official BIP-352 test vectors.
12
+ The silentpayments bindings (and `secp256k1_context_set_sha256_compression`) are attached
13
+ optionally, so the gem remains loadable with older libsecp256k1 (e.g. v0.7.x) where these
14
+ features raise `Secp256k1::Error`. Availability can be checked with `silentpayments_available?`.
15
+ - Add FFI binding for `secp256k1_context_set_sha256_compression`.
16
+ - Remove FFI binding for `secp256k1_schnorrsig_sign` which was removed in secp256k1 v0.8.0.
17
+ Use `secp256k1_schnorrsig_sign32` instead.
18
+ - Fix `generate_pubkey` to return a wrong public key when a binary private key
19
+ happens to consist of hexadecimal characters (the key was converted twice).
20
+
21
+ ## [0.3.0]
22
+
23
+ - Support secp256k1 v0.7.1.
24
+ - Add FFI bindings for the full v0.7.1 public C API.
25
+ - Add `Secp256k1::Key` module: EC key arithmetic (`tweak_add_seckey`/`tweak_mul_seckey`/`negate_seckey`, `tweak_add_pubkey`/`tweak_mul_pubkey`/`negate_pubkey`, `combine_pubkeys`), x-only public key operations (`xonly_pubkey_from_pubkey`, `xonly_tweak_add_pubkey`, `xonly_tweak_add_check?`) and key pair accessors (`keypair_to_seckey`/`keypair_to_pubkey`/`keypair_to_xonly_pubkey`).
26
+ Also adds `compare_pubkey`/`sort_pubkeys`/`compare_xonly_pubkey` and `keypair_xonly_tweak_add`.
27
+ - Add `Secp256k1::ECDH` module: `ecdh`.
28
+ - Add compact ECDSA signature conversion: `ecdsa_signature_to_compact` / `ecdsa_signature_from_compact`.
29
+ - Add `tagged_sha256` (BIP-340 tagged hash).
30
+ - Add `Secp256k1::EllSwift#ellswift_encode`.
31
+ - Add `Secp256k1::Recover#recoverable_signature_to_ecdsa`.
32
+ - Add `Secp256k1::SchnorrSig#sign_schnorr_custom` / `#verify_schnorr_custom` for variable-length messages.
33
+ - Add `Secp256k1::MuSig#generate_musig_nonce_counter` (deterministic counter-based nonce generation).
34
+
3
35
  ## [0.1.0] - 2024-05-07
4
36
 
5
37
  - Initial release
data/Gemfile CHANGED
@@ -8,3 +8,4 @@ gemspec
8
8
  gem "rake", "~> 13.0"
9
9
  gem "rspec", "~> 3.0"
10
10
  gem "csv", '~> 3.3'
11
+ gem "bech32", "~> 1.5"
data/README.md CHANGED
@@ -40,13 +40,28 @@ generate_key_pair
40
40
  See [here](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1) for available methods.
41
41
  In addition, the following modules are also included, so you can use them as they are.
42
42
 
43
+ * [Key](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/Key)
44
+ * [ECDH](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/ECDH)
43
45
  * [Recover](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/Recover)
44
46
  * [SchnorrSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SchnorrSig)
47
+ * [MuSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/MuSig)
45
48
  * [EllSwift](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/EllSwift)
49
+ * [SilentPayments](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SilentPayments)
46
50
 
47
51
  ### Compatibility
48
52
 
53
+ Since v0.8.0, the major and minor version of this gem matches the supported
54
+ secp256k1 version (e.g. secp256k1rb v0.8.x supports secp256k1 v0.8.x).
55
+ Patch releases are used for gem-side changes.
56
+
57
+ The gem also loads with older libsecp256k1 versions; features that require a newer
58
+ library (e.g. the silentpayments module) raise `Secp256k1::Error` when used.
59
+ Use `silentpayments_available?` to check availability at runtime.
60
+
61
+ Older versions:
62
+
49
63
  secp256k1 version | secp256k1rb version
50
64
  :---:|:---:
51
65
  v0.4.0 | v0.1.x
52
-
66
+ v0.6.0 | v0.2.x
67
+ v0.7.1 | v0.3.x
data/lib/secp256k1/c.rb CHANGED
@@ -11,16 +11,41 @@ module Secp256k1
11
11
  attach_function(:secp256k1_context_create, [:uint], :pointer)
12
12
  attach_function(:secp256k1_context_destroy, [:pointer], :void)
13
13
  attach_function(:secp256k1_context_randomize, [:pointer, :pointer], :int)
14
+ attach_function(:secp256k1_context_clone, [:pointer], :pointer)
15
+ attach_function(:secp256k1_context_set_error_callback, [:pointer, :pointer, :pointer], :void)
16
+ attach_function(:secp256k1_context_set_illegal_callback, [:pointer, :pointer, :pointer], :void)
17
+ attach_function(:secp256k1_selftest, [], :void)
18
+ # Preallocated context management
19
+ attach_function(:secp256k1_context_preallocated_size, [:uint], :size_t)
20
+ attach_function(:secp256k1_context_preallocated_create, [:pointer, :uint], :pointer)
21
+ attach_function(:secp256k1_context_preallocated_clone_size, [:pointer], :size_t)
22
+ attach_function(:secp256k1_context_preallocated_clone, [:pointer, :pointer], :pointer)
23
+ attach_function(:secp256k1_context_preallocated_destroy, [:pointer], :void)
14
24
  attach_function(:secp256k1_ec_pubkey_create, [:pointer, :pointer, :pointer], :int)
15
25
  attach_function(:secp256k1_ec_seckey_verify, [:pointer, :pointer], :int)
26
+ # EC key arithmetic (tweak/negate/combine/compare/sort)
27
+ attach_function(:secp256k1_ec_seckey_negate, [:pointer, :pointer], :int)
28
+ attach_function(:secp256k1_ec_seckey_tweak_add, [:pointer, :pointer, :pointer], :int)
29
+ attach_function(:secp256k1_ec_seckey_tweak_mul, [:pointer, :pointer, :pointer], :int)
30
+ attach_function(:secp256k1_ec_pubkey_negate, [:pointer, :pointer], :int)
31
+ attach_function(:secp256k1_ec_pubkey_tweak_add, [:pointer, :pointer, :pointer], :int)
32
+ attach_function(:secp256k1_ec_pubkey_tweak_mul, [:pointer, :pointer, :pointer], :int)
33
+ attach_function(:secp256k1_ec_pubkey_combine, [:pointer, :pointer, :pointer, :size_t], :int)
34
+ attach_function(:secp256k1_ec_pubkey_cmp, [:pointer, :pointer, :pointer], :int)
35
+ attach_function(:secp256k1_ec_pubkey_sort, [:pointer, :pointer, :size_t], :int)
36
+ # Utilities
37
+ attach_function(:secp256k1_tagged_sha256, [:pointer, :pointer, :pointer, :size_t, :pointer, :size_t], :int)
16
38
  attach_function(:secp256k1_ecdsa_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
17
39
  attach_function(:secp256k1_ec_pubkey_serialize, [:pointer, :pointer, :pointer, :pointer, :uint], :int)
18
40
  attach_function(:secp256k1_ecdsa_signature_serialize_der, [:pointer, :pointer, :pointer, :pointer], :int)
41
+ attach_function(:secp256k1_ecdsa_signature_serialize_compact, [:pointer, :pointer, :pointer], :int)
19
42
  attach_function(:secp256k1_ec_pubkey_parse, [:pointer, :pointer, :pointer, :size_t], :int)
20
43
  attach_function(:secp256k1_ecdsa_signature_parse_der, [:pointer, :pointer, :pointer, :size_t], :int)
44
+ attach_function(:secp256k1_ecdsa_signature_parse_compact, [:pointer, :pointer, :pointer], :int)
21
45
  attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
22
46
  attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
23
47
  attach_function(:secp256k1_schnorrsig_sign32, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
48
+ attach_function(:secp256k1_schnorrsig_sign_custom, [:pointer, :pointer, :pointer, :size_t, :pointer, :pointer], :int)
24
49
  attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
25
50
  attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
26
51
  attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
@@ -28,21 +53,34 @@ module Secp256k1
28
53
  attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
29
54
  attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
30
55
  attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
56
+ attach_function(:secp256k1_ecdsa_recoverable_signature_convert, [:pointer, :pointer, :pointer], :int)
57
+ # for ECDH module
58
+ attach_function(:secp256k1_ecdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
59
+ attach_variable(:secp256k1_ecdh_hash_function_sha256, :pointer)
60
+ attach_variable(:secp256k1_ecdh_hash_function_default, :pointer)
31
61
  # for EllSwift module
32
62
  attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
33
63
  attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
64
+ attach_function(:secp256k1_ellswift_encode, [:pointer, :pointer, :pointer, :pointer], :int)
34
65
  attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :pointer)
35
66
  attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
36
67
  # for ExtraKeys module
37
- attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
38
68
  attach_function(:secp256k1_xonly_pubkey_serialize, [:pointer, :pointer, :pointer], :int)
39
- attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
69
+ attach_function(:secp256k1_xonly_pubkey_cmp, [:pointer, :pointer, :pointer], :int)
70
+ attach_function(:secp256k1_xonly_pubkey_from_pubkey, [:pointer, :pointer, :pointer, :pointer], :int)
71
+ attach_function(:secp256k1_xonly_pubkey_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
72
+ attach_function(:secp256k1_xonly_pubkey_tweak_add_check, [:pointer, :pointer, :int, :pointer, :pointer], :int)
73
+ attach_function(:secp256k1_keypair_pub, [:pointer, :pointer, :pointer], :int)
74
+ attach_function(:secp256k1_keypair_sec, [:pointer, :pointer, :pointer], :int)
75
+ attach_function(:secp256k1_keypair_xonly_pub, [:pointer, :pointer, :pointer, :pointer], :int)
76
+ attach_function(:secp256k1_keypair_xonly_tweak_add, [:pointer, :pointer, :pointer], :int)
40
77
  # for MuSig module
41
78
  attach_function(:secp256k1_musig_pubkey_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
42
79
  attach_function(:secp256k1_musig_pubkey_get, [:pointer, :pointer, :pointer], :int)
43
80
  attach_function(:secp256k1_musig_pubkey_ec_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
44
81
  attach_function(:secp256k1_musig_pubkey_xonly_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
45
82
  attach_function(:secp256k1_musig_nonce_gen, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
83
+ attach_function(:secp256k1_musig_nonce_gen_counter, [:pointer, :pointer, :pointer, :uint64, :pointer, :pointer, :pointer, :pointer], :int)
46
84
  attach_function(:secp256k1_musig_pubnonce_parse, [:pointer, :pointer, :pointer], :int)
47
85
  attach_function(:secp256k1_musig_pubnonce_serialize, [:pointer, :pointer, :pointer], :int)
48
86
  attach_function(:secp256k1_musig_aggnonce_serialize, [:pointer, :pointer, :pointer], :int)
@@ -55,10 +93,58 @@ module Secp256k1
55
93
  attach_function(:secp256k1_musig_partial_sig_verify, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
56
94
  attach_function(:secp256k1_musig_partial_sig_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
57
95
 
96
+ # The following functions only exist in libsecp256k1 v0.8.0 or later, so they are
97
+ # attached optionally to keep this gem loadable with older libraries.
98
+
99
+ # for SilentPayments module
100
+ SILENTPAYMENTS_AVAILABLE = begin
101
+ attach_function(:secp256k1_silentpayments_sender_create_outputs, [:pointer, :pointer, :pointer, :size_t, :pointer, :pointer, :size_t, :pointer, :size_t], :int)
102
+ attach_function(:secp256k1_silentpayments_recipient_label_parse, [:pointer, :pointer, :pointer], :int)
103
+ attach_function(:secp256k1_silentpayments_recipient_label_serialize, [:pointer, :pointer, :pointer], :int)
104
+ attach_function(:secp256k1_silentpayments_recipient_label_create, [:pointer, :pointer, :pointer, :pointer, :uint32], :int)
105
+ attach_function(:secp256k1_silentpayments_recipient_create_labeled_spend_pubkey, [:pointer, :pointer, :pointer, :pointer], :int)
106
+ attach_function(:secp256k1_silentpayments_recipient_prevouts_summary_create, [:pointer, :pointer, :pointer, :pointer, :size_t, :pointer, :size_t], :int)
107
+ attach_function(:secp256k1_silentpayments_recipient_scan_outputs, [:pointer, :pointer, :pointer, :pointer, :size_t, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
108
+ true
109
+ rescue FFI::NotFoundError
110
+ false
111
+ end
112
+
113
+ SHA256_COMPRESSION_AVAILABLE = begin
114
+ attach_function(:secp256k1_context_set_sha256_compression, [:pointer, :pointer], :void)
115
+ true
116
+ rescue FFI::NotFoundError
117
+ false
118
+ end
119
+
120
+ # Whether the loaded libsecp256k1 supports the silentpayments module.
121
+ # @return [Boolean]
122
+ def self.silentpayments_available?
123
+ SILENTPAYMENTS_AVAILABLE
124
+ end
125
+
126
+ # Whether the loaded libsecp256k1 supports secp256k1_context_set_sha256_compression.
127
+ # @return [Boolean]
128
+ def self.sha256_compression_available?
129
+ SHA256_COMPRESSION_AVAILABLE
130
+ end
131
+
58
132
  # Pointer to secp256k1_ellswift_xdh_hash_function_bip324 constant.
59
133
  # @return [FFI::Pointer]
60
134
  def self.ellswift_xdh_hash_function_bip324
61
135
  FFI::Pointer.new(secp256k1_ellswift_xdh_hash_function_bip324)
62
136
  end
137
+
138
+ # Pointer to secp256k1_ecdh_hash_function_sha256 constant (the default ECDH hash function).
139
+ # @return [FFI::Pointer]
140
+ def self.ecdh_hash_function_sha256
141
+ FFI::Pointer.new(secp256k1_ecdh_hash_function_sha256)
142
+ end
143
+
144
+ # Pointer to secp256k1_ecdh_hash_function_default constant.
145
+ # @return [FFI::Pointer]
146
+ def self.ecdh_hash_function_default
147
+ FFI::Pointer.new(secp256k1_ecdh_hash_function_default)
148
+ end
63
149
  end
64
150
  end
@@ -0,0 +1,37 @@
1
+ module Secp256k1
2
+ # ECDH module.
3
+ # @example
4
+ # include Secp256k1
5
+ #
6
+ # alice_sk, alice_pk = generate_key_pair
7
+ # bob_sk, bob_pk = generate_key_pair
8
+ #
9
+ # # Both parties compute the same shared secret.
10
+ # ecdh(bob_pk, alice_sk) == ecdh(alice_pk, bob_sk)
11
+ #
12
+ module ECDH
13
+
14
+ # Compute an EC Diffie-Hellman shared secret.
15
+ # @param [String] pubkey the other party's public key with hex format.
16
+ # @param [String] private_key your private key with hex format.
17
+ # @param [FFI::Pointer] hash_function (Optional)A pointer to the hash function to use. If omitted, the
18
+ # library default(SHA256 of the compressed point) is used. See {Secp256k1::C.ecdh_hash_function_sha256}.
19
+ # @return [String] 32-byte shared secret with hex format.
20
+ # @raise [Secp256k1::Error] If the secret was invalid or the public key could not be parsed.
21
+ # @raise [ArgumentError] If invalid arguments specified.
22
+ def ecdh(pubkey, private_key, hash_function: nil)
23
+ raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
24
+ validate_string!("private_key", private_key, 32)
25
+ pubkey = hex2bin(pubkey)
26
+ private_key = hex2bin(private_key)
27
+ with_context do |context|
28
+ internal = parse_pubkey_internal(context, pubkey)
29
+ seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
30
+ output = FFI::MemoryPointer.new(:uchar, 32)
31
+ hashfp = hash_function || FFI::Pointer::NULL
32
+ raise Error, 'secp256k1_ecdh failed.' unless secp256k1_ecdh(context, output, internal, seckey, hashfp, nil) == 1
33
+ output.read_string(32).unpack1('H*')
34
+ end
35
+ end
36
+ end
37
+ end
@@ -19,6 +19,26 @@ module Secp256k1
19
19
  end
20
20
  end
21
21
 
22
+ # Encode a public key into an ElligatorSwift public key.
23
+ # @param [String] pubkey public key with hex format.
24
+ # @param [String] rnd (Optional)32-byte randomness with binary format. If omitted, random bytes are used.
25
+ # @return [String] ElligatorSwift public key with hex format(64 bytes).
26
+ # @raise [Secp256k1::Error] If encoding failed.
27
+ # @raise [ArgumentError] If invalid arguments specified.
28
+ def ellswift_encode(pubkey, rnd = nil)
29
+ raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
30
+ validate_string!("rnd", rnd, 32) if rnd
31
+ pubkey = hex2bin(pubkey)
32
+ rnd = rnd ? hex2bin(rnd) : SecureRandom.random_bytes(32)
33
+ with_context do |context|
34
+ internal = parse_pubkey_internal(context, pubkey)
35
+ ell64 = FFI::MemoryPointer.new(:uchar, 64)
36
+ rnd32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, rnd)
37
+ raise Error, 'Failed to encode ElligatorSwift public key.' unless secp256k1_ellswift_encode(context, ell64, internal, rnd32) == 1
38
+ ell64.read_string(64).unpack1('H*')
39
+ end
40
+ end
41
+
22
42
  # Compute an ElligatorSwift public key for a secret key.
23
43
  # @param [String] private_key private key with hex format
24
44
  # @return [String] ElligatorSwift public key with hex format.
@@ -0,0 +1,342 @@
1
+ module Secp256k1
2
+ # Key module provides EC key arithmetic (tweak/negate/combine), x-only public key
3
+ # operations used by Taproot, and key pair accessors.
4
+ # @example
5
+ # include Secp256k1
6
+ #
7
+ # sk, pk = generate_key_pair
8
+ # tweak = SecureRandom.bytes(32)
9
+ #
10
+ # # Tweak a private/public key.
11
+ # tweaked_sk = tweak_add_seckey(sk, tweak)
12
+ # tweaked_pk = tweak_add_pubkey(pk, tweak)
13
+ #
14
+ module Key
15
+
16
+ # Negate a private key in place and return the result.
17
+ # @param [String] private_key a private key with hex format.
18
+ # @return [String] negated private key with hex format.
19
+ # @raise [Secp256k1::Error] If the private key is invalid.
20
+ # @raise [ArgumentError] If invalid arguments specified.
21
+ def negate_seckey(private_key)
22
+ validate_string!("private_key", private_key, 32)
23
+ private_key = hex2bin(private_key)
24
+ with_context do |context|
25
+ seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
26
+ raise Error, 'secp256k1_ec_seckey_negate failed.' unless secp256k1_ec_seckey_negate(context, seckey) == 1
27
+ seckey.read_string(32).unpack1('H*')
28
+ end
29
+ end
30
+
31
+ # Tweak a private key by adding +tweak+ to it.
32
+ # @param [String] private_key a private key with hex format.
33
+ # @param [String] tweak a 32-byte tweak with hex format.
34
+ # @return [String] tweaked private key with hex format.
35
+ # @raise [Secp256k1::Error] If the arguments are invalid or the result is the zero key.
36
+ # @raise [ArgumentError] If invalid arguments specified.
37
+ def tweak_add_seckey(private_key, tweak)
38
+ tweak_seckey(private_key, tweak, :secp256k1_ec_seckey_tweak_add)
39
+ end
40
+
41
+ # Tweak a private key by multiplying it by +tweak+.
42
+ # @param [String] private_key a private key with hex format.
43
+ # @param [String] tweak a 32-byte tweak with hex format.
44
+ # @return [String] tweaked private key with hex format.
45
+ # @raise [Secp256k1::Error] If the arguments are invalid.
46
+ # @raise [ArgumentError] If invalid arguments specified.
47
+ def tweak_mul_seckey(private_key, tweak)
48
+ tweak_seckey(private_key, tweak, :secp256k1_ec_seckey_tweak_mul)
49
+ end
50
+
51
+ # Negate a public key.
52
+ # @param [String] pubkey a public key with hex format.
53
+ # @param [Boolean] compressed Whether to return a compressed public key.
54
+ # @return [String] negated public key with hex format.
55
+ # @raise [Secp256k1::Error] If the public key is invalid.
56
+ # @raise [ArgumentError] If invalid arguments specified.
57
+ def negate_pubkey(pubkey, compressed: true)
58
+ raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
59
+ pubkey = hex2bin(pubkey)
60
+ with_context do |context|
61
+ internal = parse_pubkey_internal(context, pubkey)
62
+ raise Error, 'secp256k1_ec_pubkey_negate failed.' unless secp256k1_ec_pubkey_negate(context, internal) == 1
63
+ serialize_pubkey_internal(context, internal, compressed)
64
+ end
65
+ end
66
+
67
+ # Tweak a public key by adding +tweak+ * G to it.
68
+ # @param [String] pubkey a public key with hex format.
69
+ # @param [String] tweak a 32-byte tweak with hex format.
70
+ # @param [Boolean] compressed Whether to return a compressed public key.
71
+ # @return [String] tweaked public key with hex format.
72
+ # @raise [Secp256k1::Error] If the arguments are invalid or the result is the point at infinity.
73
+ # @raise [ArgumentError] If invalid arguments specified.
74
+ def tweak_add_pubkey(pubkey, tweak, compressed: true)
75
+ tweak_pubkey(pubkey, tweak, :secp256k1_ec_pubkey_tweak_add, compressed: compressed)
76
+ end
77
+
78
+ # Tweak a public key by multiplying it by +tweak+.
79
+ # @param [String] pubkey a public key with hex format.
80
+ # @param [String] tweak a 32-byte tweak with hex format.
81
+ # @param [Boolean] compressed Whether to return a compressed public key.
82
+ # @return [String] tweaked public key with hex format.
83
+ # @raise [Secp256k1::Error] If the arguments are invalid.
84
+ # @raise [ArgumentError] If invalid arguments specified.
85
+ def tweak_mul_pubkey(pubkey, tweak, compressed: true)
86
+ tweak_pubkey(pubkey, tweak, :secp256k1_ec_pubkey_tweak_mul, compressed: compressed)
87
+ end
88
+
89
+ # Add a number of public keys together.
90
+ # @param [Array<String>] pubkeys an array of public keys with hex format.
91
+ # @param [Boolean] compressed Whether to return a compressed public key.
92
+ # @return [String] the sum of the public keys with hex format.
93
+ # @raise [Secp256k1::Error] If the sum is the point at infinity.
94
+ # @raise [ArgumentError] If invalid arguments specified.
95
+ def combine_pubkeys(pubkeys, compressed: true)
96
+ raise ArgumentError, "pubkeys must be Array." unless pubkeys.is_a?(Array)
97
+ raise ArgumentError, "pubkeys must not be empty." if pubkeys.empty?
98
+ with_context do |context|
99
+ internals = pubkeys.map do |pubkey|
100
+ raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
101
+ parse_pubkey_internal(context, hex2bin(pubkey))
102
+ end
103
+ ins = FFI::MemoryPointer.new(:pointer, internals.size)
104
+ ins.write_array_of_pointer(internals)
105
+ combined = FFI::MemoryPointer.new(:uchar, 64)
106
+ raise Error, 'secp256k1_ec_pubkey_combine failed.' unless secp256k1_ec_pubkey_combine(context, combined, ins, internals.size) == 1
107
+ serialize_pubkey_internal(context, combined, compressed)
108
+ end
109
+ end
110
+
111
+ # Convert a public key into an x-only public key.
112
+ # @param [String] pubkey a public key with hex format.
113
+ # @return [Array(String, Integer)] the x-only public key with hex format(32 bytes) and its parity(0 or 1).
114
+ # @raise [Secp256k1::Error] If the public key is invalid.
115
+ # @raise [ArgumentError] If invalid arguments specified.
116
+ def xonly_pubkey_from_pubkey(pubkey)
117
+ raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
118
+ pubkey = hex2bin(pubkey)
119
+ with_context do |context|
120
+ internal = parse_pubkey_internal(context, pubkey)
121
+ xonly = FFI::MemoryPointer.new(:uchar, 64)
122
+ parity = FFI::MemoryPointer.new(:int)
123
+ raise Error, 'secp256k1_xonly_pubkey_from_pubkey failed.' unless secp256k1_xonly_pubkey_from_pubkey(context, xonly, parity, internal) == 1
124
+ [serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
125
+ end
126
+ end
127
+
128
+ # Tweak an x-only public key by adding +tweak+ * G to it (used for Taproot output key derivation).
129
+ # @param [String] xonly_pubkey an x-only public key with hex format(32 bytes).
130
+ # @param [String] tweak a 32-byte tweak with hex format.
131
+ # @param [Boolean] compressed Whether to return a compressed public key.
132
+ # @return [Array(String, Integer)] the tweaked(full) public key with hex format and the parity(0 or 1) of the tweaked key.
133
+ # @raise [Secp256k1::Error] If the arguments are invalid.
134
+ # @raise [ArgumentError] If invalid arguments specified.
135
+ def xonly_tweak_add_pubkey(xonly_pubkey, tweak, compressed: true)
136
+ validate_string!("xonly_pubkey", xonly_pubkey, X_ONLY_PUBKEY_SIZE)
137
+ validate_string!("tweak", tweak, 32)
138
+ xonly_pubkey = hex2bin(xonly_pubkey)
139
+ tweak = hex2bin(tweak)
140
+ with_context do |context|
141
+ xonly = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, xonly_pubkey)
142
+ internal = FFI::MemoryPointer.new(:uchar, 64)
143
+ raise Error, 'An invalid x-only public key was specified.' unless secp256k1_xonly_pubkey_parse(context, internal, xonly) == 1
144
+ tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
145
+ output = FFI::MemoryPointer.new(:uchar, 64)
146
+ raise Error, 'secp256k1_xonly_pubkey_tweak_add failed.' unless secp256k1_xonly_pubkey_tweak_add(context, output, internal, tweak_ptr) == 1
147
+ _, parity = xonly_pubkey_from_internal(context, output)
148
+ [serialize_pubkey_internal(context, output, compressed), parity]
149
+ end
150
+ end
151
+
152
+ # Check that a tweaked x-only public key was computed by tweaking +internal_pubkey+ with +tweak+.
153
+ # @param [String] tweaked_pubkey32 the tweaked x-only public key with hex format(32 bytes).
154
+ # @param [Integer] tweaked_pk_parity the parity(0 or 1) of the tweaked public key.
155
+ # @param [String] internal_pubkey the internal x-only public key with hex format(32 bytes).
156
+ # @param [String] tweak a 32-byte tweak with hex format.
157
+ # @return [Boolean] verification result.
158
+ # @raise [ArgumentError] If invalid arguments specified.
159
+ def xonly_tweak_add_check?(tweaked_pubkey32, tweaked_pk_parity, internal_pubkey, tweak)
160
+ validate_string!("tweaked_pubkey32", tweaked_pubkey32, X_ONLY_PUBKEY_SIZE)
161
+ validate_string!("internal_pubkey", internal_pubkey, X_ONLY_PUBKEY_SIZE)
162
+ validate_string!("tweak", tweak, 32)
163
+ raise ArgumentError, "tweaked_pk_parity must be 0 or 1." unless [0, 1].include?(tweaked_pk_parity)
164
+ tweaked_pubkey32 = hex2bin(tweaked_pubkey32)
165
+ internal_pubkey = hex2bin(internal_pubkey)
166
+ tweak = hex2bin(tweak)
167
+ with_context do |context|
168
+ internal = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, internal_pubkey)
169
+ internal_xonly = FFI::MemoryPointer.new(:uchar, 64)
170
+ return false unless secp256k1_xonly_pubkey_parse(context, internal_xonly, internal) == 1
171
+ tweaked_ptr = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, tweaked_pubkey32)
172
+ tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
173
+ secp256k1_xonly_pubkey_tweak_add_check(context, tweaked_ptr, tweaked_pk_parity, internal_xonly, tweak_ptr) == 1
174
+ end
175
+ end
176
+
177
+ # Get the public key from a key pair.
178
+ # @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
179
+ # @param [Boolean] compressed Whether to return a compressed public key.
180
+ # @return [String] public key with hex format.
181
+ # @raise [Secp256k1::Error] If the key pair is invalid.
182
+ # @raise [ArgumentError] If invalid arguments specified.
183
+ def keypair_to_pubkey(keypair, compressed: true)
184
+ validate_string!("keypair", keypair, 96)
185
+ keypair = hex2bin(keypair)
186
+ with_context do |context|
187
+ keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
188
+ internal = FFI::MemoryPointer.new(:uchar, 64)
189
+ raise Error, 'secp256k1_keypair_pub failed.' unless secp256k1_keypair_pub(context, internal, keypair_ptr) == 1
190
+ serialize_pubkey_internal(context, internal, compressed)
191
+ end
192
+ end
193
+
194
+ # Get the private key from a key pair.
195
+ # @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
196
+ # @return [String] private key with hex format.
197
+ # @raise [Secp256k1::Error] If the key pair is invalid.
198
+ # @raise [ArgumentError] If invalid arguments specified.
199
+ def keypair_to_seckey(keypair)
200
+ validate_string!("keypair", keypair, 96)
201
+ keypair = hex2bin(keypair)
202
+ with_context do |context|
203
+ keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
204
+ seckey = FFI::MemoryPointer.new(:uchar, 32)
205
+ raise Error, 'secp256k1_keypair_sec failed.' unless secp256k1_keypair_sec(context, seckey, keypair_ptr) == 1
206
+ seckey.read_string(32).unpack1('H*')
207
+ end
208
+ end
209
+
210
+ # Get the x-only public key from a key pair.
211
+ # @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
212
+ # @return [Array(String, Integer)] the x-only public key with hex format(32 bytes) and its parity(0 or 1).
213
+ # @raise [Secp256k1::Error] If the key pair is invalid.
214
+ # @raise [ArgumentError] If invalid arguments specified.
215
+ def keypair_to_xonly_pubkey(keypair)
216
+ validate_string!("keypair", keypair, 96)
217
+ keypair = hex2bin(keypair)
218
+ with_context do |context|
219
+ keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
220
+ xonly = FFI::MemoryPointer.new(:uchar, 64)
221
+ parity = FFI::MemoryPointer.new(:int)
222
+ raise Error, 'secp256k1_keypair_xonly_pub failed.' unless secp256k1_keypair_xonly_pub(context, xonly, parity, keypair_ptr) == 1
223
+ [serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
224
+ end
225
+ end
226
+
227
+ # Compare two public keys using lexicographic(of compressed serialization) order.
228
+ # @param [String] pubkey1 a public key with hex format.
229
+ # @param [String] pubkey2 a public key with hex format.
230
+ # @return [Integer] negative if pubkey1 < pubkey2, 0 if equal, positive if pubkey1 > pubkey2.
231
+ # @raise [Secp256k1::Error] If a public key is invalid.
232
+ # @raise [ArgumentError] If invalid arguments specified.
233
+ def compare_pubkey(pubkey1, pubkey2)
234
+ raise ArgumentError, "pubkey1 must be String." unless pubkey1.is_a?(String)
235
+ raise ArgumentError, "pubkey2 must be String." unless pubkey2.is_a?(String)
236
+ with_context do |context|
237
+ a = parse_pubkey_internal(context, hex2bin(pubkey1))
238
+ b = parse_pubkey_internal(context, hex2bin(pubkey2))
239
+ secp256k1_ec_pubkey_cmp(context, a, b)
240
+ end
241
+ end
242
+
243
+ # Sort public keys using lexicographic(of compressed serialization) order.
244
+ # @param [Array<String>] pubkeys an array of public keys with hex format.
245
+ # @param [Boolean] compressed Whether to return compressed public keys.
246
+ # @return [Array<String>] the sorted public keys with hex format.
247
+ # @raise [Secp256k1::Error] If a public key is invalid.
248
+ # @raise [ArgumentError] If invalid arguments specified.
249
+ def sort_pubkeys(pubkeys, compressed: true)
250
+ raise ArgumentError, "pubkeys must be Array." unless pubkeys.is_a?(Array)
251
+ raise ArgumentError, "pubkeys must not be empty." if pubkeys.empty?
252
+ with_context do |context|
253
+ internals = pubkeys.map do |pubkey|
254
+ raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
255
+ parse_pubkey_internal(context, hex2bin(pubkey))
256
+ end
257
+ arr = FFI::MemoryPointer.new(:pointer, internals.size)
258
+ arr.write_array_of_pointer(internals)
259
+ raise Error, 'secp256k1_ec_pubkey_sort failed.' unless secp256k1_ec_pubkey_sort(context, arr, internals.size) == 1
260
+ arr.read_array_of_pointer(internals.size).map { |ptr| serialize_pubkey_internal(context, ptr, compressed) }
261
+ end
262
+ end
263
+
264
+ # Compare two x-only public keys using lexicographic order.
265
+ # @param [String] pubkey1 an x-only public key with hex format(32 bytes).
266
+ # @param [String] pubkey2 an x-only public key with hex format(32 bytes).
267
+ # @return [Integer] negative if pubkey1 < pubkey2, 0 if equal, positive if pubkey1 > pubkey2.
268
+ # @raise [Secp256k1::Error] If a public key is invalid.
269
+ # @raise [ArgumentError] If invalid arguments specified.
270
+ def compare_xonly_pubkey(pubkey1, pubkey2)
271
+ validate_string!("pubkey1", pubkey1, X_ONLY_PUBKEY_SIZE)
272
+ validate_string!("pubkey2", pubkey2, X_ONLY_PUBKEY_SIZE)
273
+ with_context do |context|
274
+ a = parse_xonly_pubkey_internal(context, hex2bin(pubkey1))
275
+ b = parse_xonly_pubkey_internal(context, hex2bin(pubkey2))
276
+ secp256k1_xonly_pubkey_cmp(context, a, b)
277
+ end
278
+ end
279
+
280
+ # Tweak a key pair by adding +tweak+ to the key pair's x-only context.
281
+ # @param [String] keypair a key pair with hex format(96 bytes), created by {#create_keypair}.
282
+ # @param [String] tweak a 32-byte tweak with hex format.
283
+ # @return [String] the tweaked key pair with hex format(96 bytes).
284
+ # @raise [Secp256k1::Error] If the arguments are invalid.
285
+ # @raise [ArgumentError] If invalid arguments specified.
286
+ def keypair_xonly_tweak_add(keypair, tweak)
287
+ validate_string!("keypair", keypair, 96)
288
+ validate_string!("tweak", tweak, 32)
289
+ keypair = hex2bin(keypair)
290
+ tweak = hex2bin(tweak)
291
+ with_context do |context|
292
+ keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
293
+ tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
294
+ raise Error, 'secp256k1_keypair_xonly_tweak_add failed.' unless secp256k1_keypair_xonly_tweak_add(context, keypair_ptr, tweak_ptr) == 1
295
+ keypair_ptr.read_string(96).unpack1('H*')
296
+ end
297
+ end
298
+
299
+ private
300
+
301
+ def parse_xonly_pubkey_internal(context, xonly_pubkey)
302
+ xonly = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE).put_bytes(0, xonly_pubkey)
303
+ internal = FFI::MemoryPointer.new(:uchar, 64)
304
+ raise Error, 'An invalid x-only public key was specified.' unless secp256k1_xonly_pubkey_parse(context, internal, xonly) == 1
305
+ internal
306
+ end
307
+
308
+ def tweak_seckey(private_key, tweak, func)
309
+ validate_string!("private_key", private_key, 32)
310
+ validate_string!("tweak", tweak, 32)
311
+ private_key = hex2bin(private_key)
312
+ tweak = hex2bin(tweak)
313
+ with_context do |context|
314
+ seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
315
+ tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
316
+ raise Error, "#{func} failed." unless send(func, context, seckey, tweak_ptr) == 1
317
+ seckey.read_string(32).unpack1('H*')
318
+ end
319
+ end
320
+
321
+ def tweak_pubkey(pubkey, tweak, func, compressed: true)
322
+ raise ArgumentError, "pubkey must be String." unless pubkey.is_a?(String)
323
+ validate_string!("tweak", tweak, 32)
324
+ pubkey = hex2bin(pubkey)
325
+ tweak = hex2bin(tweak)
326
+ with_context do |context|
327
+ internal = parse_pubkey_internal(context, pubkey)
328
+ tweak_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, tweak)
329
+ raise Error, "#{func} failed." unless send(func, context, internal, tweak_ptr) == 1
330
+ serialize_pubkey_internal(context, internal, compressed)
331
+ end
332
+ end
333
+
334
+ # Get x-only pubkey and parity from an internal(64 bytes) full pubkey pointer.
335
+ def xonly_pubkey_from_internal(context, internal_pubkey)
336
+ xonly = FFI::MemoryPointer.new(:uchar, 64)
337
+ parity = FFI::MemoryPointer.new(:int)
338
+ raise Error, 'secp256k1_xonly_pubkey_from_pubkey failed.' unless secp256k1_xonly_pubkey_from_pubkey(context, xonly, parity, internal_pubkey) == 1
339
+ [serialize_xonly_pubkey_internal(context, xonly), parity.read_int]
340
+ end
341
+ end
342
+ end