secp256k1rb 0.3.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: 6a26cd2beaa9525ea8f7a0db0e411dbb3d049ec976214f4651e787f02ed76f83
4
- data.tar.gz: 05c9fce673e0f97e48faa60335cb5de37a3be74a12c50f5cfc541621c11526f2
3
+ metadata.gz: ecd7e009765f35dce95f533c357900c4a7c4d6018cbd1f9dd3139af22b4e7c85
4
+ data.tar.gz: 0f6868a1f010cbca12e4ef7b4efe66713ae46fa6a28faf958dc3ee0ec529c8a9
5
5
  SHA512:
6
- metadata.gz: 71dbb792ccefcf0a852ff5ff96173c2f7c784cff9313489782b03159dd0163860938435d66ec974cccf7fe3755b5bd1ded9bbdf20a77a9d4d41bc2a6b65d63f9
7
- data.tar.gz: b5e36b1dae73beb4c7e2420efb792c9185fa08f96acbbd2090b5858fc305b88bd9b8a42197900bf1dfea9d1ff9c4b754d491719a63754e817c1c1854969b3db9
6
+ metadata.gz: b498f4d026083bd102410e7a8fff7cf2cb59b7f9b0f7033e0bcad9838d9e5fe718f87b9a2c9f8cb2f23439c66a4c612969d6f011fe00c24ce1a426c4a065c661
7
+ data.tar.gz: 5f1ae0a7a15d18af2c92d35ac93718c5f5d3733fd8236197277d7ba03bef3efb370560671f704f34b7b80d3ae61d80b3fc953a8b88246a4520152ab322b435b1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
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
+
3
21
  ## [0.3.0]
4
22
 
5
23
  - Support secp256k1 v0.7.1.
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
@@ -46,9 +46,20 @@ In addition, the following modules are also included, so you can use them as the
46
46
  * [SchnorrSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SchnorrSig)
47
47
  * [MuSig](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/MuSig)
48
48
  * [EllSwift](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/EllSwift)
49
+ * [SilentPayments](https://www.rubydoc.info/gems/secp256k1rb/Secp256k1/SilentPayments)
49
50
 
50
51
  ### Compatibility
51
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
+
52
63
  secp256k1 version | secp256k1rb version
53
64
  :---:|:---:
54
65
  v0.4.0 | v0.1.x
data/lib/secp256k1/c.rb CHANGED
@@ -45,7 +45,6 @@ module Secp256k1
45
45
  attach_function(:secp256k1_ecdsa_signature_normalize, [:pointer, :pointer, :pointer], :int)
46
46
  attach_function(:secp256k1_ecdsa_verify, [:pointer, :pointer, :pointer, :pointer], :int)
47
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
48
  attach_function(:secp256k1_schnorrsig_sign_custom, [:pointer, :pointer, :pointer, :size_t, :pointer, :pointer], :int)
50
49
  attach_function(:secp256k1_schnorrsig_verify, [:pointer, :pointer, :pointer, :size_t, :pointer], :int)
51
50
  attach_function(:secp256k1_keypair_create, [:pointer, :pointer, :pointer], :int)
@@ -94,6 +93,42 @@ module Secp256k1
94
93
  attach_function(:secp256k1_musig_partial_sig_verify, [:pointer, :pointer, :pointer, :pointer, :pointer, :pointer], :int)
95
94
  attach_function(:secp256k1_musig_partial_sig_agg, [:pointer, :pointer, :pointer, :pointer, :size_t], :int)
96
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
+
97
132
  # Pointer to secp256k1_ellswift_xdh_hash_function_bip324 constant.
98
133
  # @return [FFI::Pointer]
99
134
  def self.ellswift_xdh_hash_function_bip324
@@ -0,0 +1,319 @@
1
+ module Secp256k1
2
+
3
+ # Silent Payments module (BIP-352).
4
+ # This module provides elliptic-curve operations required for sending and
5
+ # receiving Silent Payments. Note that this is not a full implementation of
6
+ # BIP-352; higher-level concepts like addresses, output script types or
7
+ # transactions must be handled by the caller.
8
+ # @example
9
+ # include Secp256k1
10
+ #
11
+ # # Sender side:
12
+ # outputs = silentpayments_sender_create_outputs(
13
+ # [[scan_pubkey, spend_pubkey]], outpoint_smallest, plain_seckeys: [seckey])
14
+ #
15
+ # # Recipient side:
16
+ # prevouts_summary = silentpayments_create_prevouts_summary(
17
+ # outpoint_smallest, plain_pubkeys: [pubkey])
18
+ # found = silentpayments_scan_outputs(
19
+ # outputs, scan_key, prevouts_summary, spend_pubkey)
20
+ module SilentPayments
21
+
22
+ # Size of a serialized Silent Payments label.
23
+ SP_LABEL_SIZE = 33
24
+ # Size of an internal Silent Payments label object.
25
+ SP_LABEL_INTERNAL_SIZE = 68
26
+ # Size of a Silent Payments prevouts summary object.
27
+ SP_PREVOUTS_SUMMARY_SIZE = 101
28
+ # Maximum number of recipients sharing the same scan public key (BIP-352).
29
+ SP_RECIPIENT_GROUP_LIMIT = 2323
30
+
31
+ # Struct mirroring secp256k1_silentpayments_recipient.
32
+ class Recipient < FFI::Struct
33
+ layout :scan_pubkey, [:uchar, 64],
34
+ :spend_pubkey, [:uchar, 64],
35
+ :index, :size_t
36
+ end
37
+
38
+ # Struct mirroring secp256k1_silentpayments_found_output.
39
+ class FoundOutput < FFI::Struct
40
+ layout :output, [:uchar, 64],
41
+ :tweak, [:uchar, 32],
42
+ :found_with_label, :int,
43
+ :label, [:uchar, SP_LABEL_INTERNAL_SIZE]
44
+ end
45
+
46
+ # Whether the loaded libsecp256k1 supports the silentpayments module.
47
+ # The module is available in libsecp256k1 v0.8.0 or later.
48
+ # @return [Boolean]
49
+ def silentpayments_available?
50
+ C.silentpayments_available?
51
+ end
52
+
53
+ # Create Silent Payments outputs for recipient(s) (sender side).
54
+ # @param [Array] recipients An array of recipient's key pair. Each element is an array of
55
+ # scan public key and (labeled) spend public key with hex format(33 bytes each).
56
+ # @param [String] outpoint_smallest The smallest outpoint lexicographically from the transaction inputs(36 bytes).
57
+ # @param [Array] plain_seckeys (Optional) An array of private keys of non-taproot inputs with hex format(32 bytes each).
58
+ # @param [Array] taproot_seckeys (Optional) An array of private keys of taproot inputs with hex format(32 bytes each).
59
+ # @return [Array] An array of x-only public keys(hex format) for the taproot outputs, ordered same as +recipients+.
60
+ # @raise [Secp256k1::Error] If creation failed.
61
+ # @raise [ArgumentError] If invalid arguments specified.
62
+ def silentpayments_sender_create_outputs(recipients, outpoint_smallest, plain_seckeys: [], taproot_seckeys: [])
63
+ check_silentpayments_support!
64
+ raise ArgumentError, "recipients must be an Array." unless recipients.is_a?(Array)
65
+ raise ArgumentError, "recipients must not be empty." if recipients.empty?
66
+ plain_seckeys ||= []
67
+ taproot_seckeys ||= []
68
+ raise ArgumentError, "plain_seckeys must be an Array." unless plain_seckeys.is_a?(Array)
69
+ raise ArgumentError, "taproot_seckeys must be an Array." unless taproot_seckeys.is_a?(Array)
70
+ if plain_seckeys.empty? && taproot_seckeys.empty?
71
+ raise ArgumentError, "At least one private key(plain_seckeys or taproot_seckeys) is required."
72
+ end
73
+ validate_string!("outpoint_smallest", outpoint_smallest, 36)
74
+
75
+ with_context do |context|
76
+ recipient_structs = recipients.each_with_index.map do |recipient, i|
77
+ scan_pubkey, spend_pubkey = recipient
78
+ validate_string!("scan_pubkey", scan_pubkey, 33)
79
+ validate_string!("spend_pubkey", spend_pubkey, 33)
80
+ st = Recipient.new
81
+ st.pointer.put_bytes(0, parse_pubkey_internal(context, hex2bin(scan_pubkey)).read_string(64))
82
+ st.pointer.put_bytes(64, parse_pubkey_internal(context, hex2bin(spend_pubkey)).read_string(64))
83
+ st[:index] = i
84
+ st
85
+ end
86
+ recipients_ptr = FFI::MemoryPointer.new(:pointer, recipient_structs.length)
87
+ recipients_ptr.write_array_of_pointer(recipient_structs.map(&:pointer))
88
+
89
+ generated_outputs = Array.new(recipients.length) { FFI::MemoryPointer.new(:uchar, 64) }
90
+ generated_outputs_ptr = FFI::MemoryPointer.new(:pointer, generated_outputs.length)
91
+ generated_outputs_ptr.write_array_of_pointer(generated_outputs)
92
+
93
+ outpoint_ptr = FFI::MemoryPointer.new(:uchar, 36).put_bytes(0, hex2bin(outpoint_smallest))
94
+
95
+ keypairs = taproot_seckeys.map do |sk|
96
+ validate_string!("taproot_seckey", sk, 32)
97
+ seckey = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(sk))
98
+ keypair = FFI::MemoryPointer.new(:uchar, 96)
99
+ raise Error, "taproot_seckey is invalid." unless secp256k1_keypair_create(context, keypair, seckey) == 1
100
+ keypair
101
+ end
102
+ keypairs_ptr = if keypairs.empty?
103
+ nil
104
+ else
105
+ FFI::MemoryPointer.new(:pointer, keypairs.length).write_array_of_pointer(keypairs)
106
+ end
107
+
108
+ seckeys = plain_seckeys.map do |sk|
109
+ validate_string!("plain_seckey", sk, 32)
110
+ FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(sk))
111
+ end
112
+ seckeys_ptr = if seckeys.empty?
113
+ nil
114
+ else
115
+ FFI::MemoryPointer.new(:pointer, seckeys.length).write_array_of_pointer(seckeys)
116
+ end
117
+
118
+ result = secp256k1_silentpayments_sender_create_outputs(
119
+ context, generated_outputs_ptr, recipients_ptr, recipients.length, outpoint_ptr,
120
+ keypairs_ptr, keypairs.length, seckeys_ptr, seckeys.length)
121
+ raise Error, "secp256k1_silentpayments_sender_create_outputs failed." unless result == 1
122
+
123
+ generated_outputs.map { |output| serialize_xonly_pubkey_internal(context, output) }
124
+ end
125
+ end
126
+
127
+ # Create Silent Payments label tweak and label(recipient side).
128
+ # @param [String] scan_key The recipient's scan key with hex format(32 bytes).
129
+ # @param [Integer] m The integer for the m-th label (0 is used for change outputs).
130
+ # @return [Array(String)] An array of serialized label(33 bytes) and label tweak(32 bytes) with hex format.
131
+ # @raise [Secp256k1::Error] If creation failed.
132
+ # @raise [ArgumentError] If invalid arguments specified.
133
+ def silentpayments_create_label(scan_key, m)
134
+ check_silentpayments_support!
135
+ validate_string!("scan_key", scan_key, 32)
136
+ raise ArgumentError, "m must be Integer." unless m.is_a?(Integer)
137
+ raise ArgumentError, "m must be between 0 and 2**32-1." if m < 0 || m > 0xffffffff
138
+
139
+ with_context do |context|
140
+ label = FFI::MemoryPointer.new(:uchar, SP_LABEL_INTERNAL_SIZE)
141
+ label_tweak = FFI::MemoryPointer.new(:uchar, 32)
142
+ scan_key_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(scan_key))
143
+ result = secp256k1_silentpayments_recipient_label_create(context, label, label_tweak, scan_key_ptr, m)
144
+ raise Error, "secp256k1_silentpayments_recipient_label_create failed." unless result == 1
145
+ out33 = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE)
146
+ secp256k1_silentpayments_recipient_label_serialize(context, out33, label)
147
+ [out33.read_string(SP_LABEL_SIZE).unpack1('H*'), label_tweak.read_string(32).unpack1('H*')]
148
+ end
149
+ end
150
+
151
+ # Create Silent Payments labeled spend public key(recipient side).
152
+ # @param [String] spend_pubkey The recipient's unlabeled spend public key with hex format(33 bytes).
153
+ # @param [String] label The serialized label with hex format(33 bytes).
154
+ # @return [String] The labeled spend public key with hex format(33 bytes).
155
+ # @raise [Secp256k1::Error] If creation failed.
156
+ # @raise [ArgumentError] If invalid arguments specified.
157
+ def silentpayments_create_labeled_spend_pubkey(spend_pubkey, label)
158
+ check_silentpayments_support!
159
+ validate_string!("spend_pubkey", spend_pubkey, 33)
160
+ validate_string!("label", label, SP_LABEL_SIZE)
161
+
162
+ with_context do |context|
163
+ spend_pubkey_ptr = parse_pubkey_internal(context, hex2bin(spend_pubkey))
164
+ label_ptr = parse_label_internal(context, hex2bin(label))
165
+ labeled = FFI::MemoryPointer.new(:uchar, 64)
166
+ result = secp256k1_silentpayments_recipient_create_labeled_spend_pubkey(context, labeled, spend_pubkey_ptr, label_ptr)
167
+ raise Error, "secp256k1_silentpayments_recipient_create_labeled_spend_pubkey failed." unless result == 1
168
+ serialize_pubkey_internal(context, labeled, true)
169
+ end
170
+ end
171
+
172
+ # Compute Silent Payments prevouts summary from prevout public keys and transaction inputs(recipient side).
173
+ # @param [String] outpoint_smallest The smallest outpoint lexicographically from the transaction inputs(36 bytes).
174
+ # @param [Array] plain_pubkeys (Optional) An array of public keys of non-taproot inputs with hex format(33 bytes each).
175
+ # @param [Array] xonly_pubkeys (Optional) An array of x-only public keys of taproot inputs with hex format(32 bytes each).
176
+ # @return [String] The prevouts summary data with hex format(101 bytes).
177
+ # Note that this is an opaque representation which is not guaranteed to be
178
+ # portable between different platforms or library versions.
179
+ # @raise [Secp256k1::Error] If the transaction is not a Silent Payments transaction.
180
+ # @raise [ArgumentError] If invalid arguments specified.
181
+ def silentpayments_create_prevouts_summary(outpoint_smallest, plain_pubkeys: [], xonly_pubkeys: [])
182
+ check_silentpayments_support!
183
+ plain_pubkeys ||= []
184
+ xonly_pubkeys ||= []
185
+ raise ArgumentError, "plain_pubkeys must be an Array." unless plain_pubkeys.is_a?(Array)
186
+ raise ArgumentError, "xonly_pubkeys must be an Array." unless xonly_pubkeys.is_a?(Array)
187
+ if plain_pubkeys.empty? && xonly_pubkeys.empty?
188
+ raise ArgumentError, "At least one public key(plain_pubkeys or xonly_pubkeys) is required."
189
+ end
190
+ validate_string!("outpoint_smallest", outpoint_smallest, 36)
191
+
192
+ with_context do |context|
193
+ xonly_ptrs = xonly_pubkeys.map do |pubkey|
194
+ validate_string!("xonly_pubkey", pubkey, 32)
195
+ parse_xonly_pubkey_internal(context, hex2bin(pubkey))
196
+ end
197
+ plain_ptrs = plain_pubkeys.map do |pubkey|
198
+ validate_string!("plain_pubkey", pubkey, 33)
199
+ parse_pubkey_internal(context, hex2bin(pubkey))
200
+ end
201
+ xonly_arr = if xonly_ptrs.empty?
202
+ nil
203
+ else
204
+ FFI::MemoryPointer.new(:pointer, xonly_ptrs.length).write_array_of_pointer(xonly_ptrs)
205
+ end
206
+ plain_arr = if plain_ptrs.empty?
207
+ nil
208
+ else
209
+ FFI::MemoryPointer.new(:pointer, plain_ptrs.length).write_array_of_pointer(plain_ptrs)
210
+ end
211
+ outpoint_ptr = FFI::MemoryPointer.new(:uchar, 36).put_bytes(0, hex2bin(outpoint_smallest))
212
+ prevouts_summary = FFI::MemoryPointer.new(:uchar, SP_PREVOUTS_SUMMARY_SIZE)
213
+ result = secp256k1_silentpayments_recipient_prevouts_summary_create(
214
+ context, prevouts_summary, outpoint_ptr, xonly_arr, xonly_ptrs.length, plain_arr, plain_ptrs.length)
215
+ raise Error, "secp256k1_silentpayments_recipient_prevouts_summary_create failed." unless result == 1
216
+ prevouts_summary.read_string(SP_PREVOUTS_SUMMARY_SIZE).unpack1('H*')
217
+ end
218
+ end
219
+
220
+ # Scan for Silent Payments transaction outputs(recipient side).
221
+ # @param [Array] tx_outputs An array of the transaction's x-only public key outputs with hex format(32 bytes each),
222
+ # in their original transaction (vout) order.
223
+ # @param [String] scan_key The recipient's scan key with hex format(32 bytes).
224
+ # @param [String] prevouts_summary The prevouts summary data created by #silentpayments_create_prevouts_summary
225
+ # with hex format(101 bytes).
226
+ # @param [String] spend_pubkey The recipient's unlabeled spend public key with hex format(33 bytes).
227
+ # @param [Hash] labels (Optional) The recipient's label cache. A hash whose key is a serialized label(33 bytes)
228
+ # and whose value is the corresponding label tweak(32 bytes), both with hex format.
229
+ # Can be created with #silentpayments_create_label.
230
+ # @return [Array] An array of found outputs. Each element is a hash with the following keys:
231
+ # - :output x-only public key of the found output with hex format(32 bytes).
232
+ # - :tweak The 32-byte tweak needed to spend the output with hex format.
233
+ # - :label The serialized label(33 bytes with hex format) if the output was sent to a labeled address, otherwise nil.
234
+ # @raise [Secp256k1::Error] If the transaction is not a Silent Payments transaction or arguments are invalid.
235
+ # @raise [ArgumentError] If invalid arguments specified.
236
+ def silentpayments_scan_outputs(tx_outputs, scan_key, prevouts_summary, spend_pubkey, labels: nil)
237
+ check_silentpayments_support!
238
+ raise ArgumentError, "tx_outputs must be an Array." unless tx_outputs.is_a?(Array)
239
+ raise ArgumentError, "tx_outputs must not be empty." if tx_outputs.empty?
240
+ validate_string!("scan_key", scan_key, 32)
241
+ validate_string!("prevouts_summary", prevouts_summary, SP_PREVOUTS_SUMMARY_SIZE)
242
+ validate_string!("spend_pubkey", spend_pubkey, 33)
243
+ raise ArgumentError, "labels must be a Hash." if labels && !labels.is_a?(Hash)
244
+
245
+ with_context do |context|
246
+ output_ptrs = tx_outputs.map do |output|
247
+ validate_string!("tx_output", output, 32)
248
+ parse_xonly_pubkey_internal(context, hex2bin(output))
249
+ end
250
+ tx_outputs_ptr = FFI::MemoryPointer.new(:pointer, output_ptrs.length).write_array_of_pointer(output_ptrs)
251
+
252
+ found_outputs = Array.new(tx_outputs.length) { FoundOutput.new }
253
+ found_outputs_ptr = FFI::MemoryPointer.new(:pointer, found_outputs.length)
254
+ found_outputs_ptr.write_array_of_pointer(found_outputs.map(&:pointer))
255
+ n_found = FFI::MemoryPointer.new(:uint32)
256
+
257
+ scan_key_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(scan_key))
258
+ prevouts_summary_ptr = FFI::MemoryPointer.new(:uchar, SP_PREVOUTS_SUMMARY_SIZE).
259
+ put_bytes(0, hex2bin(prevouts_summary))
260
+ spend_pubkey_ptr = parse_pubkey_internal(context, hex2bin(spend_pubkey))
261
+
262
+ label_lookup = nil
263
+ if labels
264
+ # Keep tweak pointers referenced in this hash so they remain valid while scanning.
265
+ tweak_cache = {}
266
+ labels.each do |label, tweak|
267
+ validate_string!("label", label, SP_LABEL_SIZE)
268
+ validate_string!("label_tweak", tweak, 32)
269
+ tweak_cache[hex2bin(label)] = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(tweak))
270
+ end
271
+ label_lookup = FFI::Function.new(:pointer, [:pointer, :pointer]) do |label33, _|
272
+ tweak_cache[label33.read_string(SP_LABEL_SIZE)] || FFI::Pointer::NULL
273
+ end
274
+ end
275
+
276
+ result = secp256k1_silentpayments_recipient_scan_outputs(
277
+ context, found_outputs_ptr, n_found, tx_outputs_ptr, tx_outputs.length,
278
+ scan_key_ptr, prevouts_summary_ptr, spend_pubkey_ptr, label_lookup, nil)
279
+ raise Error, "secp256k1_silentpayments_recipient_scan_outputs failed." unless result == 1
280
+
281
+ Array.new(n_found.read_uint32) do |i|
282
+ found = found_outputs[i]
283
+ label = if found[:found_with_label] != 0
284
+ out33 = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE)
285
+ secp256k1_silentpayments_recipient_label_serialize(
286
+ context, out33, found.pointer + FoundOutput.offset_of(:label))
287
+ out33.read_string(SP_LABEL_SIZE).unpack1('H*')
288
+ end
289
+ {
290
+ output: serialize_xonly_pubkey_internal(context, found.pointer),
291
+ tweak: found.pointer.get_bytes(FoundOutput.offset_of(:tweak), 32).unpack1('H*'),
292
+ label: label
293
+ }
294
+ end
295
+ end
296
+ end
297
+
298
+ private
299
+
300
+ # @raise [Secp256k1::Error] If the loaded libsecp256k1 does not support the silentpayments module.
301
+ def check_silentpayments_support!
302
+ unless C.silentpayments_available?
303
+ raise Error, "The loaded libsecp256k1 does not support the silentpayments module. libsecp256k1 v0.8.0 or later is required."
304
+ end
305
+ end
306
+
307
+ # Parse a serialized label into an internal label pointer.
308
+ # @param [FFI::Pointer] context secp256k1 context.
309
+ # @param [String] label serialized label with binary format(33 bytes).
310
+ # @return [FFI::Pointer] internal label pointer(68 bytes).
311
+ # @raise [Secp256k1::Error] If the label is invalid.
312
+ def parse_label_internal(context, label)
313
+ label_ptr = FFI::MemoryPointer.new(:uchar, SP_LABEL_SIZE).put_bytes(0, label)
314
+ internal = FFI::MemoryPointer.new(:uchar, SP_LABEL_INTERNAL_SIZE)
315
+ raise Error, 'An invalid label was specified.' unless secp256k1_silentpayments_recipient_label_parse(context, internal, label_ptr) == 1
316
+ internal
317
+ end
318
+ end
319
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Secp256k1
4
- VERSION = "0.3.0"
4
+ VERSION = "0.8.0"
5
5
  end
data/lib/secp256k1.rb CHANGED
@@ -9,6 +9,7 @@ require_relative 'secp256k1/recovery'
9
9
  require_relative 'secp256k1/ellswift'
10
10
  require_relative 'secp256k1/schnorrsig'
11
11
  require_relative 'secp256k1/musig'
12
+ require_relative 'secp256k1/silentpayments'
12
13
 
13
14
  # Binding for secp256k1 (https://github.com/bitcoin-core/secp256k1/)
14
15
  # @example
@@ -36,6 +37,7 @@ module Secp256k1
36
37
  include SchnorrSig
37
38
  include EllSwift
38
39
  include MuSig
40
+ include SilentPayments
39
41
 
40
42
  FLAGS_TYPE_MASK = ((1 << 8) - 1)
41
43
  FLAGS_TYPE_CONTEXT = (1 << 0)
@@ -88,8 +90,8 @@ module Secp256k1
88
90
  private_key = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, SecureRandom.random_bytes(32))
89
91
  ret = secp256k1_ec_seckey_verify(context, private_key)
90
92
  end
91
- private_key = private_key.read_string(32).unpack1('H*')
92
- [private_key , generate_pubkey_in_context(context, private_key, compressed: compressed) ]
93
+ private_key = private_key.read_string(32)
94
+ [private_key.unpack1('H*'), generate_pubkey_in_context(context, private_key, compressed: compressed)]
93
95
  end
94
96
  end
95
97
 
@@ -323,9 +325,12 @@ module Secp256k1
323
325
  output.read_string(X_ONLY_PUBKEY_SIZE).unpack1('H*')
324
326
  end
325
327
 
328
+ # Generate public key from +private_key+ using given +context+.
329
+ # +private_key+ must be a 32-byte binary string, not hex, to avoid a second
330
+ # hex conversion corrupting binary keys that happen to look like hex strings.
326
331
  def generate_pubkey_in_context(context, private_key, compressed: true)
327
332
  internal_pubkey = FFI::MemoryPointer.new(:uchar, 64)
328
- priv_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, hex2bin(private_key))
333
+ priv_ptr = FFI::MemoryPointer.new(:uchar, 32).put_bytes(0, private_key)
329
334
  result = secp256k1_ec_pubkey_create(context, internal_pubkey, priv_ptr)
330
335
  raise 'error creating pubkey' unless result
331
336
  serialize_pubkey_internal(context, internal_pubkey, compressed)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: secp256k1rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - azuchi
@@ -51,6 +51,7 @@ files:
51
51
  - lib/secp256k1/musig/session.rb
52
52
  - lib/secp256k1/recovery.rb
53
53
  - lib/secp256k1/schnorrsig.rb
54
+ - lib/secp256k1/silentpayments.rb
54
55
  - lib/secp256k1/version.rb
55
56
  - secp256k1rb.gemspec
56
57
  - sig/secp256k1.rbs