secp256k1rb 0.1.1 → 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: aff0d24f08ccdd2fb1d622ca2cb8655a9e36d3960890710861878e32e55850b3
4
- data.tar.gz: 6915105a7e248f5a1ca810e5f0b8af3dea75731a7b65bd111df74a8691f2d907
3
+ metadata.gz: 6a26cd2beaa9525ea8f7a0db0e411dbb3d049ec976214f4651e787f02ed76f83
4
+ data.tar.gz: 05c9fce673e0f97e48faa60335cb5de37a3be74a12c50f5cfc541621c11526f2
5
5
  SHA512:
6
- metadata.gz: 307ef35d2afa388b1640a2df6438ca19b14cc5d1bf1920f509e2f1768d610d0534f926ad0122122b5ad30da9ab6a9da3f49b17b6d1215fc0bd1e1fc55c42ab71
7
- data.tar.gz: a533565583b3af85dbfbb9f259e3c644a4de3ec5a8c52e08b37eb46ff0bdba922e5a14cd3f2588792caa1ae4934881b9563b045c6907dda50571349a97dcef9c
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
@@ -1,8 +1,6 @@
1
1
  # secp256k1rb
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/secp256k1`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ This is a Ruby binding for Bitcoin Core's [secp256k1 library](https://github.com/bitcoin-core/secp256k1/).
6
4
 
7
5
  ## Installation
8
6
 
@@ -22,22 +20,37 @@ Or install it yourself as:
22
20
 
23
21
  ## Usage
24
22
 
25
- TODO: Write usage instructions here
23
+ To use this library, you need to specify the path of the secp256k1 shared library in environment variable
24
+ `SECP256K1_LIB_PATH`, e.g: `$ export SECP256K1_LIB_PATH=/var/local/lib/libsecp256k1.so`.
26
25
 
27
- ## Development
26
+ Note: This library also implements the recovery module, so you must have built the secp256k1 library with the
27
+ `--enable-module-recovery` option.
28
28
 
29
- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
29
+ By including the Secp256k1 module, you can use the features provided by the `libsepc256k1` library. For example:
30
30
 
31
- To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
31
+ ```ruby
32
+ require 'secp256k1'
32
33
 
33
- ## Contributing
34
+ include Secp256k1
34
35
 
35
- Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/secp256k1. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://github.com/[USERNAME]/secp256k1/blob/master/CODE_OF_CONDUCT.md).
36
+ generate_key_pair
37
+ => ["e00c2ae99e59b5262be3d507d026081f0e6cf9972ffdd4f2d45a390f7a41b053", "027e0f70b540d627422cf7bb77d86ae1bb6829c80104dd48dc2539e6277ea25624"]
38
+ ```
36
39
 
37
- ## License
40
+ See [here](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1) for available methods.
41
+ In addition, the following modules are also included, so you can use them as they are.
38
42
 
39
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
43
+ * [Key](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/Key)
44
+ * [ECDH](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/ECDH)
45
+ * [Recover](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/Recover)
46
+ * [SchnorrSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SchnorrSig)
47
+ * [MuSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/MuSig)
48
+ * [EllSwift](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/EllSwift)
40
49
 
41
- ## Code of Conduct
50
+ ### Compatibility
42
51
 
43
- Everyone interacting in the Secp256k1 project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the [code of conduct](https://github.com/[USERNAME]/secp256k1/blob/master/CODE_OF_CONDUCT.md).
52
+ secp256k1 version | secp256k1rb version
53
+ :---:|:---:
54
+ v0.4.0 | v0.1.x
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,15 +54,62 @@ 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)
62
+ # for EllSwift module
31
63
  attach_function(:secp256k1_ellswift_decode, [:pointer, :pointer, :pointer], :int)
32
64
  attach_function(:secp256k1_ellswift_create, [:pointer, :pointer, :pointer, :pointer], :int)
65
+ attach_function(:secp256k1_ellswift_encode, [:pointer, :pointer, :pointer, :pointer], :int)
33
66
  attach_variable(:secp256k1_ellswift_xdh_hash_function_bip324, :pointer)
34
67
  attach_function(:secp256k1_ellswift_xdh, [:pointer, :pointer, :pointer, :pointer, :pointer, :int, :pointer, :pointer], :int)
68
+ # for ExtraKeys module
69
+ attach_function(:secp256k1_xonly_pubkey_serialize, [: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)
78
+ # for MuSig module
79
+ attach_function(:secp256k1_musig_pubkey_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
80
+ attach_function(:secp256k1_musig_pubkey_get, [:pointer, :pointer, :pointer], :int)
81
+ attach_function(:secp256k1_musig_pubkey_ec_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
82
+ attach_function(:secp256k1_musig_pubkey_xonly_tweak_add, [:pointer, :pointer, :pointer, :pointer], :int)
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)
85
+ attach_function(:secp256k1_musig_pubnonce_parse, [:pointer, :pointer, :pointer], :int)
86
+ attach_function(:secp256k1_musig_pubnonce_serialize, [:pointer, :pointer, :pointer], :int)
87
+ attach_function(:secp256k1_musig_aggnonce_serialize, [:pointer, :pointer, :pointer], :int)
88
+ attach_function(:secp256k1_musig_aggnonce_parse, [:pointer, :pointer, :pointer], :int)
89
+ attach_function(:secp256k1_musig_nonce_agg, [:pointer, :pointer, :pointer, :size_t], :int)
90
+ attach_function(:secp256k1_musig_nonce_process, [:pointer, :pointer, :pointer, :pointer, :pointer], :int)
91
+ attach_function(:secp256k1_musig_partial_sign, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
92
+ attach_function(:secp256k1_musig_partial_sig_serialize, [:pointer, :pointer, :pointer], :int)
93
+ attach_function(:secp256k1_musig_partial_sig_parse, [:pointer, :pointer, :pointer], :int)
94
+ attach_function(:secp256k1_musig_partial_sig_verify, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
95
+ attach_function(:secp256k1_musig_partial_sig_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
35
96
 
36
97
  # Pointer to secp256k1_ellswift_xdh_hash_function_bip324 constant.
37
98
  # @return [FFI::Pointer]
38
99
  def self.ellswift_xdh_hash_function_bip324
39
100
  FFI::Pointer.new(secp256k1_ellswift_xdh_hash_function_bip324)
40
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
41
114
  end
42
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
@@ -8,9 +8,8 @@ module Secp256k1
8
8
  # @raise [Secp256k1::Error] If decode failed.
9
9
  # @raise [ArgumentError] If invalid arguments specified.
10
10
  def ellswift_decode(ell_key, compressed: true)
11
- raise ArgumentError, "ell_key must be String." unless ell_key.is_a?(String)
11
+ validate_string!("ell_key", ell_key, ELL_SWIFT_KEY_SIZE)
12
12
  ell_key = hex2bin(ell_key)
13
- raise ArgumentError, "ell_key must be 64 bytes." unless ell_key.bytesize == 64
14
13
  with_context do |context|
15
14
  ell64 = FFI::MemoryPointer.new(:uchar, ell_key.bytesize).put_bytes(0, ell_key)
16
15
  internal = FFI::MemoryPointer.new(:uchar, 64)
@@ -20,15 +19,34 @@ module Secp256k1
20
19
  end
21
20
  end
22
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
+
23
42
  # Compute an ElligatorSwift public key for a secret key.
24
43
  # @param [String] private_key private key with hex format
25
44
  # @return [String] ElligatorSwift public key with hex format.
26
45
  # @raise [Secp256k1::Error] If failed to create elligattor swhift public key.
27
46
  # @raise [ArgumentError] If invalid arguments specified.
28
47
  def ellswift_create(private_key)
29
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
48
+ validate_string!("private_key", private_key, 32)
30
49
  private_key = hex2bin(private_key)
31
- raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
32
50
  with_context(flags: CONTEXT_SIGN) do |context|
33
51
  ell64 = FFI::MemoryPointer.new(:uchar, 64)
34
52
  seckey32 = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
@@ -46,15 +64,12 @@ module Secp256k1
46
64
  # @return [String] x coordinate with hex format.
47
65
  # @raise [Secp256k1::Error] If secret is invalid or hashfp return 0.
48
66
  def ellswift_ecdh_xonly(their_ell_pubkey, our_ell_pubkey, private_key, initiating)
49
- raise ArgumentError, "their_ell_pubkey must be String." unless their_ell_pubkey.is_a?(String)
50
- raise ArgumentError, "our_ell_pubkey must be String." unless our_ell_pubkey.is_a?(String)
51
- raise ArgumentError, "private_key must be String." unless private_key.is_a?(String)
67
+ validate_string!("their_ell_pubkey", their_ell_pubkey, ELL_SWIFT_KEY_SIZE)
68
+ validate_string!("our_ell_pubkey", our_ell_pubkey, ELL_SWIFT_KEY_SIZE)
69
+ validate_string!("private_key", private_key, 32)
52
70
  their_ell_pubkey = hex2bin(their_ell_pubkey)
53
71
  our_ell_pubkey = hex2bin(our_ell_pubkey)
54
72
  private_key = hex2bin(private_key)
55
- raise ArgumentError, "their_ell_pubkey must be #{ELL_SWIFT_KEY_SIZE} bytes." unless their_ell_pubkey.bytesize == ELL_SWIFT_KEY_SIZE
56
- raise ArgumentError, "our_ell_pubkey must be #{ELL_SWIFT_KEY_SIZE} bytes." unless our_ell_pubkey.bytesize == ELL_SWIFT_KEY_SIZE
57
- raise ArgumentError, "private_key must be 32 bytes." unless private_key.bytesize == 32
58
73
 
59
74
  with_context(flags: CONTEXT_SIGN) do |context|
60
75
  output = FFI::MemoryPointer.new(:uchar, 32)
@@ -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