secp256k1rb 0.2.0 → 0.3.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: 6a26cd2beaa9525ea8f7a0db0e411dbb3d049ec976214f4651e787f02ed76f83
4
+ data.tar.gz: 05c9fce673e0f97e48faa60335cb5de37a3be74a12c50f5cfc541621c11526f2
5
5
  SHA512:
6
- metadata.gz: 14177d6aa88872ae15db021b45d492ec59b083ad751d224d852c9f5440fd2a9b51855608a0c0456175aadd09b06d68591122b799fc2a7d08724c4f726c84c980
7
- data.tar.gz: '0805a6d29bad9054f581115919365a3e8623adc8d93f64e92d18435f33012056a227ccbb4bd08c5421ec4a3bbc1e85f94d8b116cd47d4c205f51e0efe5306f00'
6
+ metadata.gz: 71dbb792ccefcf0a852ff5ff96173c2f7c784cff9313489782b03159dd0163860938435d66ec974cccf7fe3755b5bd1ded9bbdf20a77a9d4d41bc2a6b65d63f9
7
+ data.tar.gz: b5e36b1dae73beb4c7e2420efb792c9185fa08f96acbbd2090b5858fc305b88bd9b8a42197900bf1dfea9d1ff9c4b754d491719a63754e817c1c1854969b3db9
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,19 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0]
4
+
5
+ - Support secp256k1 v0.7.1.
6
+ - Add FFI bindings for the full v0.7.1 public C API.
7
+ - 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`).
8
+ Also adds `compare_pubkey`/`sort_pubkeys`/`compare_xonly_pubkey` and `keypair_xonly_tweak_add`.
9
+ - Add `Secp256k1::ECDH` module: `ecdh`.
10
+ - Add compact ECDSA signature conversion: `ecdsa_signature_to_compact` / `ecdsa_signature_from_compact`.
11
+ - Add `tagged_sha256` (BIP-340 tagged hash).
12
+ - Add `Secp256k1::EllSwift#ellswift_encode`.
13
+ - Add `Secp256k1::Recover#recoverable_signature_to_ecdsa`.
14
+ - Add `Secp256k1::SchnorrSig#sign_schnorr_custom` / `#verify_schnorr_custom` for variable-length messages.
15
+ - Add `Secp256k1::MuSig#generate_musig_nonce_counter` (deterministic counter-based nonce generation).
16
+
3
17
  ## [0.1.0] - 2024-05-07
4
18
 
5
19
  - Initial release
data/README.md CHANGED
@@ -40,8 +40,11 @@ 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)
46
49
 
47
50
  ### Compatibility
@@ -49,4 +52,5 @@ In addition, the following modules are also included, so you can use them as the
49
52
  secp256k1 version | secp256k1rb version
50
53
  :---:|:---:
51
54
  v0.4.0 | v0.1.x
52
-
55
+ v0.6.0 | v0.2.x
56
+ v0.7.1 | v0.3.x
data/lib/secp256k1/c.rb CHANGED
@@ -11,16 +11,42 @@ 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, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
49
+ attach_function(:secp256k1_schnorrsig_sign_custom, [:pointer, :pointer, :pointer, :size_t, :pointer, :pointer], :int)
24
50
  attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
25
51
  attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
26
52
  attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
@@ -28,21 +54,34 @@ module Secp256k1
28
54
  attach_function(:secp256k1_ecdsa_recoverable_signature_serialize_compact, [:pointer, :pointer, :pointer, :pointer], :int)
29
55
  attach_function(:secp256k1_ecdsa_recover, [:pointer, :pointer, :pointer, :pointer], :int)
30
56
  attach_function(:secp256k1_ecdsa_recoverable_signature_parse_compact, [:pointer, :pointer, :pointer, :int], :int)
57
+ attach_function(:secp256k1_ecdsa_recoverable_signature_convert, [:pointer, :pointer, :pointer], :int)
58
+ # for ECDH module
59
+ attach_function(:secp256k1_ecdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
60
+ attach_variable(:secp256k1_ecdh_hash_function_sha256, :pointer)
61
+ attach_variable(:secp256k1_ecdh_hash_function_default, :pointer)
31
62
  # for EllSwift module
32
63
  attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
33
64
  attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
65
+ attach_function(:secp256k1_ellswift_encode, [:pointer, :pointer, :pointer, :pointer], :int)
34
66
  attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :pointer)
35
67
  attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
36
68
  # for ExtraKeys module
37
- attach_function(:secp256k1_xonly_pubkey_parse, [:pointer, :pointer, :pointer], :int)
38
69
  attach_function(:secp256k1_xonly_pubkey_serialize, [:pointer, :pointer, :pointer], :int)
39
- attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
70
+ attach_function(:secp256k1_xonly_pubkey_cmp, [:pointer, :pointer, :pointer], :int)
71
+ attach_function(:secp256k1_xonly_pubkey_from_pubkey, [:pointer, :pointer, :pointer, :pointer], :int)
72
+ attach_function(:secp256k1_xonly_pubkey_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
73
+ attach_function(:secp256k1_xonly_pubkey_tweak_add_check, [:pointer, :pointer, :int, :pointer, :pointer], :int)
74
+ attach_function(:secp256k1_keypair_pub, [:pointer, :pointer, :pointer], :int)
75
+ attach_function(:secp256k1_keypair_sec, [:pointer, :pointer, :pointer], :int)
76
+ attach_function(:secp256k1_keypair_xonly_pub, [:pointer, :pointer, :pointer, :pointer], :int)
77
+ attach_function(:secp256k1_keypair_xonly_tweak_add, [:pointer, :pointer, :pointer], :int)
40
78
  # for MuSig module
41
79
  attach_function(:secp256k1_musig_pubkey_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
42
80
  attach_function(:secp256k1_musig_pubkey_get, [:pointer, :pointer, :pointer], :int)
43
81
  attach_function(:secp256k1_musig_pubkey_ec_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
44
82
  attach_function(:secp256k1_musig_pubkey_xonly_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
45
83
  attach_function(:secp256k1_musig_nonce_gen, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
84
+ attach_function(:secp256k1_musig_nonce_gen_counter, [:pointer, :pointer, :pointer, :uint64, :pointer, :pointer, :pointer, :pointer], :int)
46
85
  attach_function(:secp256k1_musig_pubnonce_parse, [:pointer, :pointer, :pointer], :int)
47
86
  attach_function(:secp256k1_musig_pubnonce_serialize, [:pointer, :pointer, :pointer], :int)
48
87
  attach_function(:secp256k1_musig_aggnonce_serialize, [:pointer, :pointer, :pointer], :int)
@@ -60,5 +99,17 @@ module Secp256k1
60
99
  def self.ellswift_xdh_hash_function_bip324
61
100
  FFI::Pointer.new(secp256k1_ellswift_xdh_hash_function_bip324)
62
101
  end
102
+
103
+ # Pointer to secp256k1_ecdh_hash_function_sha256 constant (the default ECDH hash function).
104
+ # @return [FFI::Pointer]
105
+ def self.ecdh_hash_function_sha256
106
+ FFI::Pointer.new(secp256k1_ecdh_hash_function_sha256)
107
+ end
108
+
109
+ # Pointer to secp256k1_ecdh_hash_function_default constant.
110
+ # @return [FFI::Pointer]
111
+ def self.ecdh_hash_function_default
112
+ FFI::Pointer.new(secp256k1_ecdh_hash_function_default)
113
+ end
63
114
  end
64
115
  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
@@ -132,6 +132,44 @@ module Secp256k1
132
132
  end
133
133
  end
134
134
 
135
+ # Generate a nonce pair deterministically using a non-repeating counter instead of session randomness.
136
+ # The caller must ensure that +counter+ never repeats for the same key pair.
137
+ # @param [Integer] counter A non-repeating counter(uint64).
138
+ # @param [String] private_key The private key for which the partial signature is generated, with hex format.
139
+ # @param [Secp256k1::MuSig::KeyAggContext] key_agg_ctx (Optional) The aggregated public key context.
140
+ # @param [String] msg (Optional) The message to be signed.
141
+ # @param [String] extra_in (Optional) The auxiliary input.
142
+ # @return [Array(String)] The array of secret nonce and public nonce with hex format.
143
+ # @raise [ArgumentError] If invalid arguments specified.
144
+ # @raise [Secp256k1::Error]
145
+ def generate_musig_nonce_counter(counter, private_key, key_agg_ctx: nil, msg: nil, extra_in: nil)
146
+ raise ArgumentError, "counter must be Integer." unless counter.is_a?(Integer)
147
+ raise ArgumentError, "counter must be between 0 and 2**64-1." if counter < 0 || counter > (2**64 - 1)
148
+ validate_string!("private_key", private_key, 32)
149
+ validate_string!("msg", msg, 32) if msg
150
+ validate_string!("extra_in", extra_in, 32) if extra_in
151
+ if key_agg_ctx
152
+ raise ArgumentError, "key_agg_ctx must be Secp256k1::MuSig::KeyAggContext." unless key_agg_ctx.is_a?(KeyAggContext)
153
+ end
154
+
155
+ with_context do |context|
156
+ keypair = [create_keypair(private_key)].pack('H*')
157
+ keypair_ptr = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
158
+ pubnonce = FFI::MemoryPointer.new(:uchar, 132)
159
+ secnonce = FFI::MemoryPointer.new(:uchar, 132)
160
+ msg32 = msg ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(msg)) : nil
161
+ extra_input32 = extra_in ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(extra_in)) : nil
162
+ cache_ptr = key_agg_ctx ? key_agg_ctx.pointer : nil
163
+
164
+ raise Error, "arguments is invalid." unless secp256k1_musig_nonce_gen_counter(
165
+ context, secnonce, pubnonce, counter, keypair_ptr, msg32, cache_ptr, extra_input32) == 1
166
+
167
+ pub66 = FFI::MemoryPointer.new(:uchar, 66)
168
+ secp256k1_musig_pubnonce_serialize(context, pub66, pubnonce)
169
+ [secnonce.read_string(132).unpack1('H*'), pub66.read_string(66).unpack1('H*')]
170
+ end
171
+ end
172
+
135
173
  # Aggregates the nonces of all signers into a single nonce.
136
174
  # @param [Array] pub_nonces An array of public nonces sent by the signers.
137
175
  # @return [String] An aggregated public nonce.
@@ -68,5 +68,28 @@ module Secp256k1
68
68
  serialize_pubkey_internal(context, pubkey.read_string(64), compressed)
69
69
  end
70
70
  end
71
+
72
+ # Convert a recoverable signature into a normal DER-encoded ECDSA signature.
73
+ # @param [String] signature The recoverable signature with binary format(65 bytes), as accepted by {#recover}.
74
+ # @return [String] DER-encoded signature with binary format.
75
+ # @raise [Secp256k1::Error] If conversion failed.
76
+ # @raise [ArgumentError] If invalid arguments specified.
77
+ def recoverable_signature_to_ecdsa(signature)
78
+ validate_string!("signature", signature, 65)
79
+ signature = hex2bin(signature)
80
+ rec = (signature[0].ord - 0x1b) & 3
81
+ raise ArgumentError, "rec must be between 0 and 3." if rec < 0 || rec > 3
82
+ with_context do |context|
83
+ recoverable = FFI::MemoryPointer.new(:uchar, 65)
84
+ input = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature[1..-1])
85
+ raise Error, 'secp256k1_ecdsa_recoverable_signature_parse_compact failed.' unless secp256k1_ecdsa_recoverable_signature_parse_compact(context, recoverable, input, rec) == 1
86
+ internal_signature = FFI::MemoryPointer.new(:uchar, 64)
87
+ raise Error, 'secp256k1_ecdsa_recoverable_signature_convert failed.' unless secp256k1_ecdsa_recoverable_signature_convert(context, internal_signature, recoverable) == 1
88
+ output = FFI::MemoryPointer.new(:uchar, 72)
89
+ output_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 72)
90
+ raise Error, 'secp256k1_ecdsa_signature_serialize_der failed.' unless secp256k1_ecdsa_signature_serialize_der(context, output, output_len, internal_signature) == 1
91
+ output.read_string(output_len.read_uint64)
92
+ end
93
+ end
71
94
  end
72
95
  end
@@ -36,7 +36,44 @@ module Secp256k1
36
36
  end
37
37
  end
38
38
 
39
- # Verify ecdsa signature.
39
+ # Magic bytes for secp256k1_schnorrsig_extraparams.
40
+ SCHNORRSIG_EXTRAPARAMS_MAGIC = [0xda, 0x6f, 0xb3, 0x8c].pack('C*').freeze
41
+
42
+ # Sign to a variable-length message using schnorr (BIP340 sign with custom parameters).
43
+ # @param [String] data The message being signed with binary format. Unlike {#sign_schnorr}, the length is arbitrary.
44
+ # @param [String] private_key a private key with hex format using sign.
45
+ # @param [String] aux_rand (Optional)The 32-byte extra entropy.
46
+ # @return [String] signature data with binary format(64 bytes).
47
+ # @raise [Secp256k1::Error] If signing failed.
48
+ # @raise [ArgumentError] If invalid arguments specified.
49
+ def sign_schnorr_custom(data, private_key, aux_rand = nil)
50
+ raise ArgumentError, "data must be String." unless data.is_a?(String)
51
+ validate_string!("private_key", private_key, 32)
52
+ validate_string!("aux_rand", aux_rand, 32) if aux_rand
53
+ private_key = hex2bin(private_key)
54
+ data = hex2bin(data)
55
+ aux_rand = hex2bin(aux_rand) if aux_rand
56
+
57
+ with_context do |context|
58
+ keypair = [create_keypair(private_key)].pack('H*')
59
+ keypair = FFI::MemoryPointer.new(:uchar, 96).put_bytes(0, keypair)
60
+ signature = FFI::MemoryPointer.new(:uchar, 64)
61
+ msg = FFI::MemoryPointer.new(:uchar, [data.bytesize, 1].max).put_bytes(0, data)
62
+
63
+ # Build secp256k1_schnorrsig_extraparams: magic[4], noncefp(NULL=default BIP340), ndata(aux_rand or NULL).
64
+ ptr_size = FFI::Pointer.size
65
+ extraparams = FFI::MemoryPointer.new(:uchar, ptr_size * 3)
66
+ extraparams.put_bytes(0, SCHNORRSIG_EXTRAPARAMS_MAGIC)
67
+ extraparams.put_pointer(ptr_size, FFI::Pointer::NULL)
68
+ ndata = aux_rand ? FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, aux_rand) : FFI::Pointer::NULL
69
+ extraparams.put_pointer(ptr_size * 2, ndata)
70
+
71
+ raise Error, 'Failed to generate schnorr signature.' unless secp256k1_schnorrsig_sign_custom(context, signature, msg, data.bytesize, keypair, extraparams) == 1
72
+ signature.read_string(64)
73
+ end
74
+ end
75
+
76
+ # Verify schnorr signature.
40
77
  # @param [String] data The 32-byte message hash assumed to be signed.
41
78
  # @param [String] signature signature data with binary format
42
79
  # @param [String] pubkey a public key with hex format using verify.
@@ -44,6 +81,23 @@ module Secp256k1
44
81
  # @raise [ArgumentError] If invalid arguments specified.
45
82
  def verify_schnorr(data, signature, pubkey)
46
83
  validate_string!("data", data, 32)
84
+ verify_schnorr_internal(data, signature, pubkey)
85
+ end
86
+
87
+ # Verify a schnorr signature over a variable-length message (counterpart of {#sign_schnorr_custom}).
88
+ # @param [String] data The message assumed to be signed. The length is arbitrary.
89
+ # @param [String] signature signature data with binary format(64 bytes).
90
+ # @param [String] pubkey an x-only public key with hex format(32 bytes) using verify.
91
+ # @return [Boolean] verification result.
92
+ # @raise [ArgumentError] If invalid arguments specified.
93
+ def verify_schnorr_custom(data, signature, pubkey)
94
+ raise ArgumentError, "data must be String." unless data.is_a?(String)
95
+ verify_schnorr_internal(data, signature, pubkey)
96
+ end
97
+
98
+ private
99
+
100
+ def verify_schnorr_internal(data, signature, pubkey)
47
101
  validate_string!("signature", signature, 64)
48
102
  validate_string!("pubkey", pubkey, 32)
49
103
  data = hex2bin(data)
@@ -54,8 +108,8 @@ module Secp256k1
54
108
  pubkey = [full_pubkey_from_xonly_pubkey(pubkey)].pack('H*')
55
109
  xonly_pubkey = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
56
110
  signature = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature)
57
- msg32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, data)
58
- result = secp256k1_schnorrsig_verify(context, signature, msg32, 32, xonly_pubkey)
111
+ msg = FFI::MemoryPointer.new(:uchar, [data.bytesize, 1].max).put_bytes(0, data)
112
+ result = secp256k1_schnorrsig_verify(context, signature, msg, data.bytesize, xonly_pubkey)
59
113
  result == 1
60
114
  end
61
115
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secp256k1
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end
data/lib/secp256k1.rb CHANGED
@@ -3,6 +3,8 @@
3
3
  require 'securerandom'
4
4
  require_relative "secp256k1/version"
5
5
  require_relative 'secp256k1/c'
6
+ require_relative 'secp256k1/key'
7
+ require_relative 'secp256k1/ecdh'
6
8
  require_relative 'secp256k1/recovery'
7
9
  require_relative 'secp256k1/ellswift'
8
10
  require_relative 'secp256k1/schnorrsig'
@@ -28,6 +30,8 @@ module Secp256k1
28
30
  class Error < StandardError; end
29
31
 
30
32
  include C
33
+ include Key
34
+ include ECDH
31
35
  include Recover
32
36
  include SchnorrSig
33
37
  include EllSwift
@@ -223,6 +227,64 @@ module Secp256k1
223
227
  end
224
228
  end
225
229
 
230
+ # Convert a DER-encoded ECDSA signature into compact(64 bytes) format.
231
+ # @param [String] signature DER-encoded signature with binary format.
232
+ # @return [String] compact signature(64 bytes) with binary format.
233
+ # @raise [Secp256k1::Error] If the signature could not be parsed.
234
+ # @raise [ArgumentError] If invalid arguments specified.
235
+ def ecdsa_signature_to_compact(signature)
236
+ raise ArgumentError, "signature must be String." unless signature.is_a?(String)
237
+ signature = hex2bin(signature)
238
+ with_context do |context|
239
+ sig_ptr = FFI::MemoryPointer.new(:uchar, signature.bytesize).put_bytes(0, signature)
240
+ internal_signature = FFI::MemoryPointer.new(:uchar, 64)
241
+ raise Error, 'secp256k1_ecdsa_signature_parse_der failed.' unless secp256k1_ecdsa_signature_parse_der(context, internal_signature, sig_ptr, signature.bytesize) == 1
242
+ output = FFI::MemoryPointer.new(:uchar, 64)
243
+ secp256k1_ecdsa_signature_serialize_compact(context, output, internal_signature)
244
+ output.read_string(64)
245
+ end
246
+ end
247
+
248
+ # Convert a compact(64 bytes) ECDSA signature into DER-encoded format.
249
+ # @param [String] signature compact signature(64 bytes) with binary format.
250
+ # @return [String] DER-encoded signature with binary format.
251
+ # @raise [Secp256k1::Error] If the signature could not be parsed.
252
+ # @raise [ArgumentError] If invalid arguments specified.
253
+ def ecdsa_signature_from_compact(signature)
254
+ validate_string!("signature", signature, 64)
255
+ signature = hex2bin(signature)
256
+ with_context do |context|
257
+ sig_ptr = FFI::MemoryPointer.new(:uchar, 64).put_bytes(0, signature)
258
+ internal_signature = FFI::MemoryPointer.new(:uchar, 64)
259
+ raise Error, 'secp256k1_ecdsa_signature_parse_compact failed.' unless secp256k1_ecdsa_signature_parse_compact(context, internal_signature, sig_ptr) == 1
260
+ output = FFI::MemoryPointer.new(:uchar, 72)
261
+ output_len = FFI::MemoryPointer.new(:uint64).put_uint64(0, 72)
262
+ raise Error, 'secp256k1_ecdsa_signature_serialize_der failed.' unless secp256k1_ecdsa_signature_serialize_der(context, output, output_len, internal_signature) == 1
263
+ output.read_string(output_len.read_uint64)
264
+ end
265
+ end
266
+
267
+ # Compute a tagged hash as defined in BIP-340: SHA256(SHA256(tag) || SHA256(tag) || msg).
268
+ # @param [String] tag the tag with binary format.
269
+ # @param [String] msg the message with binary format.
270
+ # @return [String] 32-byte tagged hash with hex format.
271
+ # @raise [Secp256k1::Error] If the hash could not be computed.
272
+ # @raise [ArgumentError] If invalid arguments specified.
273
+ def tagged_sha256(tag, msg)
274
+ raise ArgumentError, "tag must be String." unless tag.is_a?(String)
275
+ raise ArgumentError, "msg must be String." unless msg.is_a?(String)
276
+ tag = hex2bin(tag)
277
+ msg = hex2bin(msg)
278
+ with_context do |context|
279
+ hash32 = FFI::MemoryPointer.new(:uchar, 32)
280
+ # The library requires non-NULL pointers even for zero-length input, so allocate at least 1 byte.
281
+ tag_ptr = FFI::MemoryPointer.new(:uchar, [tag.bytesize, 1].max).put_bytes(0, tag)
282
+ msg_ptr = FFI::MemoryPointer.new(:uchar, [msg.bytesize, 1].max).put_bytes(0, msg)
283
+ raise Error, 'secp256k1_tagged_sha256 failed.' unless secp256k1_tagged_sha256(context, hash32, tag_ptr, tag.bytesize, msg_ptr, msg.bytesize) == 1
284
+ hash32.read_string(32).unpack1('H*')
285
+ end
286
+ end
287
+
226
288
  private
227
289
 
228
290
  # Calculate full public key(64 bytes) from public key(32 bytes).
@@ -239,6 +301,28 @@ module Secp256k1
239
301
  end
240
302
  end
241
303
 
304
+ # Parse a serialized public key into an internal 64-byte pubkey pointer.
305
+ # @param [FFI::Pointer] context secp256k1 context.
306
+ # @param [String] pubkey serialized public key with binary format.
307
+ # @return [FFI::Pointer] internal pubkey pointer(64 bytes).
308
+ # @raise [Secp256k1::Error] If the public key is invalid.
309
+ def parse_pubkey_internal(context, pubkey)
310
+ pubkey_ptr = FFI::MemoryPointer.new(:uchar, pubkey.bytesize).put_bytes(0, pubkey)
311
+ internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
312
+ raise Error, 'An invalid public key was specified.' unless secp256k1_ec_pubkey_parse(context, internal_pubkey, pubkey_ptr, pubkey.bytesize) == 1
313
+ internal_pubkey
314
+ end
315
+
316
+ # Serialize an internal x-only public key pointer(64 bytes) to 32-byte hex.
317
+ # @param [FFI::Pointer] context secp256k1 context.
318
+ # @param [FFI::Pointer] xonly_input internal x-only pubkey pointer(64 bytes).
319
+ # @return [String] x-only public key with hex format(32 bytes).
320
+ def serialize_xonly_pubkey_internal(context, xonly_input)
321
+ output = FFI::MemoryPointer.new(:uchar, X_ONLY_PUBKEY_SIZE)
322
+ secp256k1_xonly_pubkey_serialize(context, output, xonly_input)
323
+ output.read_string(X_ONLY_PUBKEY_SIZE).unpack1('H*')
324
+ end
325
+
242
326
  def generate_pubkey_in_context(context, private_key, compressed: true)
243
327
  internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
244
328
  priv_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(private_key))
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secp256k1rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2024-12-07 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: ffi
@@ -32,6 +31,8 @@ extensions: []
32
31
  extra_rdoc_files: []
33
32
  files:
34
33
  - ".rspec"
34
+ - ".ruby-gemset"
35
+ - ".ruby-version"
35
36
  - CHANGELOG.md
36
37
  - CODE_OF_CONDUCT.md
37
38
  - Gemfile
@@ -42,7 +43,9 @@ files:
42
43
  - bin/setup
43
44
  - lib/secp256k1.rb
44
45
  - lib/secp256k1/c.rb
46
+ - lib/secp256k1/ecdh.rb
45
47
  - lib/secp256k1/ellswift.rb
48
+ - lib/secp256k1/key.rb
46
49
  - lib/secp256k1/musig.rb
47
50
  - lib/secp256k1/musig/key_agg.rb
48
51
  - lib/secp256k1/musig/session.rb
@@ -58,7 +61,6 @@ metadata:
58
61
  homepage_uri: https://github.com/azuchi/secp256k1rb
59
62
  source_code_uri: https://github.com/azuchi/secp256k1rb
60
63
  changelog_uri: https://github.com/azuchi/secp256k1rb
61
- post_install_message:
62
64
  rdoc_options: []
63
65
  require_paths:
64
66
  - lib
@@ -73,8 +75,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
73
75
  - !ruby/object:Gem::Version
74
76
  version: '0'
75
77
  requirements: []
76
- rubygems_version: 3.2.3
77
- signing_key:
78
+ rubygems_version: 3.6.9
78
79
  specification_version: 4
79
80
  summary: Ruby binding for libsecp256k1.
80
81
  test_files: []